Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
b08a06c
Add Boldwin-Rapid adapter with tests and configuration files
BoldwinDev Aug 8, 2025
25e8722
Update method parameters in `boldwin_rapid` adapter to discard unused…
BoldwinDev Aug 8, 2025
01671a1
Merge branch 'master' into boldwin-rapid-adapter
BoldwinDev Aug 8, 2025
472ba65
Update HTTP headers in `boldwin_rapid` adapter to include `IP` and fi…
BoldwinDev Aug 8, 2025
950cccd
Simplify error handling in `MakeRequests` method of `boldwin_rapid` a…
BoldwinDev Aug 8, 2025
1536181
Add `Ip` field to exemplary JSON files in `boldwin_rapid` tests
BoldwinDev Aug 8, 2025
236d9f5
Add additional unit tests for error handling in `MakeRequests` and he…
BoldwinDev Aug 8, 2025
9960e04
Add unit test for direct `buildEndpointURL` error handling in `boldwi…
BoldwinDev Aug 8, 2025
20f1aa9
Merge branch 'prebid:master' into boldwin-rapid-adapter
BoldwinDev Sep 11, 2025
c559040
Changes were made to the code based on comments from SyntaxNode
BoldwinDev Sep 11, 2025
3fac16e
Merge branch 'prebid:master' into boldwin-rapid-adapter
BoldwinDev Sep 18, 2025
e4792b5
Exclude `boldwin_rapid` from core bidder validation in unit tests bas…
BoldwinDev Sep 19, 2025
72e8afb
Merge branch 'master' into boldwin-rapid-adapter
BoldwinDev Oct 9, 2025
b9f1984
Added empty-check for Currency field
BoldwinDev Oct 9, 2025
729e1c1
Added openrtb version to boldwin_rapid.yaml; Added several changes to…
BoldwinDev Oct 17, 2025
38a38c3
Move getHeaders() call outside the impression loop to avoid creating
BoldwinDev Oct 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
172 changes: 172 additions & 0 deletions adapters/boldwin_rapid/boldwin_rapid.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
package boldwin_rapid

import (
"encoding/json"
"fmt"
"text/template"

"net/http"

"github.com/prebid/openrtb/v20/openrtb2"
"github.com/prebid/prebid-server/v3/adapters"
"github.com/prebid/prebid-server/v3/config"
"github.com/prebid/prebid-server/v3/macros"
"github.com/prebid/prebid-server/v3/openrtb_ext"
"github.com/prebid/prebid-server/v3/util/jsonutil"
)

type adapter struct {
//endpoint string
endpoint *template.Template
}

func Builder(_ openrtb_ext.BidderName, config config.Adapter, _ config.Server) (adapters.Bidder, error) {
endpointTemplate, err := template.New("endpointTemplate").Parse(config.Endpoint)
if err != nil {
return nil, fmt.Errorf("unable to parse endpoint template: %v", err)
}

bidder := &adapter{
endpoint: endpointTemplate,
}

return bidder, nil
}

func (a adapter) buildEndpointURL(boldwinExt openrtb_ext.ImpExtBoldwinRapid) (string, error) {
endpointParams := macros.EndpointTemplateParams{
PublisherID: boldwinExt.Pid,
PlacementID: boldwinExt.Tid,
}

return macros.ResolveMacros(a.endpoint, endpointParams)
}

func (a *adapter) MakeRequests(request *openrtb2.BidRequest, _ *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
var adapterRequests []*adapters.RequestData

reqCopy := *request

for _, imp := range request.Imp {
// Create a new request with just this impression
reqCopy.Imp = []openrtb2.Imp{imp}

var bidderExt adapters.ExtImpBidder
var boldwinExt openrtb_ext.ImpExtBoldwinRapid

// Use the current impression's Ext
if err := jsonutil.Unmarshal(imp.Ext, &bidderExt); err != nil {
return nil, []error{err}
Copy link
Contributor

Choose a reason for hiding this comment

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

can you please add test coverage for these unmarshal errors

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Tests added

}

if err := jsonutil.Unmarshal(bidderExt.Bidder, &boldwinExt); err != nil {
return nil, []error{err}
}

endpoint, err := a.buildEndpointURL(boldwinExt)
if err != nil {
return nil, []error{err}
}

adapterReq, err := a.makeRequest(&reqCopy, endpoint)
Copy link
Contributor

Choose a reason for hiding this comment

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

getHeaders(request), that gets called inside a.makeRequest(&reqCopy, endpoint) at every turn in this for loop, doesn't seem to need any information specific to a particular imp. Therefore, we
seem to be building the headers more times than needed. In order to optimize, can we extract it out of the for loop?

44   func (a *adapter) MakeRequests(request *openrtb2.BidRequest, _ *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
45       var adapterRequests []*adapters.RequestData
46  
47       reqCopy := *request
   +     headers := a.getHeaders(request)
48  
49       for _, imp := range request.Imp {
50           // Create a new request with just this impression
51           reqCopy.Imp = []openrtb2.Imp{imp}
52  
53 *-- 16 lines: var bidderExt adapters.ExtImpBidder------------------------------------------------------------------------------
69  
70 -         adapterReq, err := a.makeRequest(&reqCopy, endpoint)
   +         adapterReq, err := a.makeRequest(&reqCopy, endpoint, headers)
71           if err != nil {
72               return nil, []error{err}
73           }
74  
75           if adapterReq != nil {
76               adapterRequests = append(adapterRequests, adapterReq)
77           }
78       }
adapters/boldwin_rapid/boldwin_rapid.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.

@guscarreon
Changes have been done.
Could you please re-review the code?

if err != nil {
return nil, []error{err}
}

if adapterReq != nil {
adapterRequests = append(adapterRequests, adapterReq)
}
}

return adapterRequests, nil
}

func (a *adapter) getHeaders(request *openrtb2.BidRequest) http.Header {
headers := http.Header{}
headers.Add("Content-Type", "application/json;charset=utf-8")
headers.Add("Accept", "application/json")
headers.Add("x-openrtb-version", "2.5")
Copy link
Contributor

@ccorbo ccorbo Oct 16, 2025

Choose a reason for hiding this comment

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

if you support openrtb version 2.5, can you please declare openrtb version 2.5 in your .yaml file. Example here under bidder info: https://docs.prebid.org/prebid-server/developers/add-new-bidder-go.html

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 version indication to the yaml file
openrtb: version: 2.5

headers.Add("Host", "rtb.beardfleet.com") // required header for the request

if request.Device != nil {
if request.Device.UA != "" {
headers.Add("User-Agent", request.Device.UA)
}

if len(request.Device.IPv6) > 0 {
headers.Add("X-Forwarded-For", request.Device.IPv6)
Copy link
Contributor

Choose a reason for hiding this comment

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

add test coverage here via json framework

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Tests added

}

if len(request.Device.IP) > 0 {
headers.Set("X-Forwarded-For", request.Device.IP)
headers.Add("IP", request.Device.IP)
Copy link
Contributor

Choose a reason for hiding this comment

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

In the edge case where both request.Device.IP and request.Device.IPv6 are non-empty, this logic prioritizes request.Device.IP over request.Device.IPv6. Is this intended? Or should it be the other way around?

 90     if request.Device != nil {
 91         if request.Device.UA != "" {
 92             headers.Add("User-Agent", request.Device.UA)
 93         }
 94 -
 95 -       if len(request.Device.IPv6) > 0 {
 96 -           headers.Add("X-Forwarded-For", request.Device.IPv6)
 97 -       }
 98
 99         if len(request.Device.IP) > 0 {
100             headers.Add("X-Forwarded-For", request.Device.IP)
101             headers.Add("IP", request.Device.IP)
102         }
    +
    +       if len(request.Device.IPv6) > 0 {
    +           headers.Add("X-Forwarded-For", request.Device.IPv6)
    +       }
103     }
adapters/boldwin_rapid/boldwin_rapid.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.

Thanks for catching it, It's better for us to prioritize the request.Device.IP over request.Device.IPv6

}
}

return headers
}

func (a *adapter) makeRequest(request *openrtb2.BidRequest, endpoint string) (*adapters.RequestData, error) {
reqJSON, err := json.Marshal(request)
if err != nil {
return nil, err
}

headers := a.getHeaders(request)

return &adapters.RequestData{
Method: "POST",
Uri: endpoint,
Body: reqJSON,
Headers: headers,
ImpIDs: openrtb_ext.GetImpIDs(request.Imp),
}, nil
}

func (a *adapter) MakeBids(request *openrtb2.BidRequest, _ *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) {
if adapters.IsResponseStatusCodeNoContent(responseData) {
return nil, nil
}

err := adapters.CheckResponseStatusCodeForErrors(responseData)
if err != nil {
return nil, []error{err}
}

var response openrtb2.BidResponse
if err := jsonutil.Unmarshal(responseData.Body, &response); err != nil {
return nil, []error{err}
}

bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp))
bidResponse.Currency = response.Cur
Copy link
Contributor

Choose a reason for hiding this comment

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

Should response.Cur get empty-checked just so we don't risk substituting bidResponse.Currency's default value of "USD", with an empty string?

137
138     bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp))
139 -   bidResponse.Currency = response.Cur
    +   if len(response.Cur) > 0 {
    +       bidResponse.Currency = response.Cur
    +   }
140
141     for _, seatBid := range response.SeatBid {
adapters/boldwin_rapid/boldwin_rapid.go


for _, seatBid := range response.SeatBid {
for i := range seatBid.Bid {
bidType, err := getBidMediaType(&seatBid.Bid[i])
if err != nil {
return nil, []error{err}
}

b := &adapters.TypedBid{
Bid: &seatBid.Bid[i],
BidType: bidType,
}
bidResponse.Bids = append(bidResponse.Bids, b)
}
}
return bidResponse, nil
}

func getBidMediaType(bid *openrtb2.Bid) (openrtb_ext.BidType, error) {
switch bid.MType {
case openrtb2.MarkupBanner:
return openrtb_ext.BidTypeBanner, nil
case openrtb2.MarkupVideo:
return openrtb_ext.BidTypeVideo, nil
case openrtb2.MarkupNative:
return openrtb_ext.BidTypeNative, nil
default:
return "", fmt.Errorf("Unable to fetch mediaType in multi-format: %s", bid.ImpID)
}
}
Loading
Loading