Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
95 changes: 95 additions & 0 deletions adapters/ezoic/ezoic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package ezoic

import (
"fmt"
"net/http"

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

type adapter struct {
endpoint string
}

// Builder builds a new instance of the Ezoic adapter for the given bidder with the given config.
func Builder(bidderName openrtb_ext.BidderName, cfg config.Adapter, server config.Server) (adapters.Bidder, error) {
return &adapter{endpoint: cfg.Endpoint}, nil
}

// MakeRequests forwards the OpenRTB request to the Ezoic bidder endpoint
// unchanged. Eligibility, demand selection, and creative construction all
// happen server-side at Ezoic; the adapter is a deliberately thin transport.
func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
body, err := jsonutil.Marshal(request)
if err != nil {
return nil, []error{fmt.Errorf("unable to marshal openrtb request: %v", err)}

@przemkaczmarek przemkaczmarek Jun 30, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Please use %w instead of %v here so the error is properly wrapped and can be inspected via errors.Is() / errors.As(). Other adapters in PBS consistently use %w for this pattern.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done in 477ec0a — switched to %w so the marshal error wraps properly for errors.Is()/errors.As().

}

headers := http.Header{}
headers.Add("Content-Type", "application/json;charset=utf-8")
headers.Add("Accept", "application/json")

return []*adapters.RequestData{{
Method: http.MethodPost,
Uri: a.endpoint,
Body: body,
Headers: headers,
ImpIDs: openrtb_ext.GetImpIDs(request.Imp),
}}, nil
}

// MakeBids unpacks the Ezoic endpoint's OpenRTB BidResponse.
func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.RequestData, responseData *adapters.ResponseData) (*adapters.BidderResponse, []error) {
if adapters.IsResponseStatusCodeNoContent(responseData) {
return nil, nil
}
if err := adapters.CheckResponseStatusCodeForErrors(responseData); err != nil {
return nil, []error{err}
}

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

bidderResponse := adapters.NewBidderResponseWithBidsCapacity(len(request.Imp))
if bidResp.Cur != "" {
bidderResponse.Currency = bidResp.Cur
}

var errs []error
for _, seatBid := range bidResp.SeatBid {
for i := range seatBid.Bid {
bidType, err := getMediaTypeForBid(seatBid.Bid[i])
if err != nil {
errs = append(errs, err)
continue
}
bidderResponse.Bids = append(bidderResponse.Bids, &adapters.TypedBid{
Bid: &seatBid.Bid[i],
BidType: bidType,
})
}
}
return bidderResponse, errs
}

func getMediaTypeForBid(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 "", &errortypes.BadServerResponse{
Message: fmt.Sprintf("unsupported mtype %d for bid %s", bid.MType, bid.ID),
}
}
}
57 changes: 57 additions & 0 deletions adapters/ezoic/ezoic_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package ezoic

import (
"testing"

"github.com/prebid/openrtb/v20/openrtb2"
"github.com/prebid/prebid-server/v4/adapters"
"github.com/prebid/prebid-server/v4/adapters/adapterstest"
"github.com/prebid/prebid-server/v4/config"
"github.com/prebid/prebid-server/v4/openrtb_ext"
"github.com/stretchr/testify/assert"
)

const testsBidderEndpoint = "https://g.ezoic.net/ezoic/prebid/adapter/ortb"

func TestJsonSamples(t *testing.T) {
bidder, buildErr := Builder(openrtb_ext.BidderEzoic, config.Adapter{
Endpoint: testsBidderEndpoint}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 347, DataCenter: "2"})

if buildErr != nil {
t.Fatalf("Builder returned unexpected error %v", buildErr)
}
Comment on lines +20 to +22

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The server config fields ExternalUrl, GvlID, and DataCenter can be removed — the Builder function does not use the server parameter at all, so these values have no effect on the test.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done in 477ec0a — the Builder ignores the server arg, so I replaced it with config.Server{}.


adapterstest.RunJSONBidderTest(t, "ezoictest", bidder)
}

func TestNoContentResponse(t *testing.T) {
bidder, buildErr := Builder(openrtb_ext.BidderEzoic, config.Adapter{
Endpoint: testsBidderEndpoint}, config.Server{})
if buildErr != nil {
t.Fatalf("Builder returned unexpected error %v", buildErr)
}

bidResponse, errs := bidder.MakeBids(nil, nil, &adapters.ResponseData{StatusCode: 204})
assert.Nil(t, bidResponse)
assert.Empty(t, errs)
}

func TestGetMediaTypeForBid(t *testing.T) {
bidType, err := getMediaTypeForBid(openrtb2.Bid{MType: openrtb2.MarkupBanner})
assert.NoError(t, err)
assert.Equal(t, openrtb_ext.BidTypeBanner, bidType)

bidType, err = getMediaTypeForBid(openrtb2.Bid{MType: openrtb2.MarkupVideo})
assert.NoError(t, err)
assert.Equal(t, openrtb_ext.BidTypeVideo, bidType)

bidType, err = getMediaTypeForBid(openrtb2.Bid{MType: openrtb2.MarkupNative})
assert.NoError(t, err)
assert.Equal(t, openrtb_ext.BidTypeNative, bidType)

_, err = getMediaTypeForBid(openrtb2.Bid{ID: "no-mtype"})
assert.Error(t, err)

_, err = getMediaTypeForBid(openrtb2.Bid{ID: "audio", MType: openrtb2.MarkupAudio})
assert.Error(t, err)
}
124 changes: 124 additions & 0 deletions adapters/ezoic/ezoictest/exemplary/simple-banner.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
{

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The adapter declares banner, video, and native support in the YAML, but the exemplary test directory only contains simple-banner.json. Please add simple-video.json and simple-native.json to ezoictest/exemplary/ to exercise the full mtype mapping end-to-end, not just in the unit test.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Done in 477ec0a — added simple-video.json (mtype 2 → video) and simple-native.json (mtype 4 → native) to ezoictest/exemplary/ so all three advertised media types now have end-to-end round-trip coverage.

"mockBidRequest": {
"id": "test-request-id",
"tmax": 750,
"site": {
"page": "https://example.com/article",
"domain": "example.com"
},
"device": {
"ua": "test-user-agent",
"ip": "203.0.113.9"
},
"imp": [
{
"id": "test-imp-id",
"tagid": "div-gpt-ad-1",
"bidfloor": 0.25,
"bidfloorcur": "USD",
"banner": {
"format": [
{
"w": 300,
"h": 250
}
]
},
"ext": {
"bidder": {
"placementId": "ezoic-placement-1"
}
}
}
]
},
"httpCalls": [
{
"expectedRequest": {
"uri": "https://g.ezoic.net/ezoic/prebid/adapter/ortb",
"body": {
"id": "test-request-id",
"tmax": 750,
"site": {
"page": "https://example.com/article",
"domain": "example.com"
},
"device": {
"ua": "test-user-agent",
"ip": "203.0.113.9"
},
"imp": [
{
"id": "test-imp-id",
"tagid": "div-gpt-ad-1",
"bidfloor": 0.25,
"bidfloorcur": "USD",
"banner": {
"format": [
{
"w": 300,
"h": 250
}
]
},
"ext": {
"bidder": {
"placementId": "ezoic-placement-1"
}
}
}
]
},
"impIDs": [
"test-imp-id"
]
},
"mockResponse": {
"status": 200,
"body": {
"id": "test-request-id",
"cur": "USD",
"seatbid": [
{
"seat": "ezoic",
"bid": [
{
"id": "ezoic-bid-1",
"impid": "test-imp-id",
"price": 2.5,
"adm": "<div>ezoic-self-tracking-creative</div>",
"crid": "ezoic-creative-1",
"w": 300,
"h": 250,
"mtype": 1,
"exp": 300
}
]
}
]
}
}
}
],
"expectedBidResponses": [
{
"currency": "USD",
"bids": [
{
"bid": {
"id": "ezoic-bid-1",
"impid": "test-imp-id",
"price": 2.5,
"adm": "<div>ezoic-self-tracking-creative</div>",
"crid": "ezoic-creative-1",
"w": 300,
"h": 250,
"mtype": 1,
"exp": 300
},
"type": "banner"
}
]
}
]
}
69 changes: 69 additions & 0 deletions adapters/ezoic/ezoictest/supplemental/invalid-response.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
{
"mockBidRequest": {
"id": "test-request-id",
"site": {
"page": "https://example.com/article",
"domain": "example.com"
},
"imp": [
{
"id": "test-imp-id",
"banner": {
"format": [
{
"w": 300,
"h": 250
}
]
},
"ext": {
"bidder": {}
}
}
]
},
"httpCalls": [
{
"expectedRequest": {
"uri": "https://g.ezoic.net/ezoic/prebid/adapter/ortb",
"body": {
"id": "test-request-id",
"site": {
"page": "https://example.com/article",
"domain": "example.com"
},
"imp": [
{
"id": "test-imp-id",
"banner": {
"format": [
{
"w": 300,
"h": 250
}
]
},
"ext": {
"bidder": {}
}
}
]
},
"impIDs": [
"test-imp-id"
]
},
"mockResponse": {
"status": 200,
"body": "invalid response"
}
}
],
"expectedBidResponses": [],
"expectedMakeBidsErrors": [
{
"value": "expect",
"comparison": "regex"
}
]
}
Loading
Loading