Skip to content

Commit 2c47c33

Browse files
pm-nikhil-vaidyaprnvgupta
authored andcommitted
Modules: Exitpoint Stage (prebid#4435)
1 parent 5aa5916 commit 2c47c33

File tree

17 files changed

+885
-4
lines changed

17 files changed

+885
-4
lines changed

endpoints/openrtb2/amp_auction.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,10 +412,13 @@ func sendAmpResponse(
412412
// nevertheless we will keep it as such for compatibility reasons.
413413
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
414414

415+
// Exitpoint will modify the response and set response headers according to hook implementation.
416+
finalResponse := hookExecutor.ExecuteExitpointStage(ampResponse, w)
417+
415418
// If an error happens when encoding the response, there isn't much we can do.
416419
// If we've sent _any_ bytes, then Go would have sent the 200 status code first.
417420
// That status code can't be un-sent... so the best we can do is log the error.
418-
if err := enc.Encode(ampResponse); err != nil {
421+
if err := enc.Encode(finalResponse); err != nil {
419422
labels.RequestStatus = metrics.RequestStatusNetworkErr
420423
ao.Errors = append(ao.Errors, fmt.Errorf("/openrtb2/amp Failed to send response: %v", err))
421424
}

endpoints/openrtb2/amp_auction_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2199,6 +2199,11 @@ func TestValidAmpResponseWhenRequestRejected(t *testing.T) {
21992199
},
22002200
},
22012201
},
2202+
{
2203+
description: "assert response returned by exitpoint stage",
2204+
file: "sample-requests/hooks/amp_exitpoint.json",
2205+
planBuilder: mockPlanBuilder{exitpointPlan: makePlan[hookstage.Exitpoint](mockUpdateResponseHook{})},
2206+
},
22022207
}
22032208

22042209
for _, tc := range testCases {

endpoints/openrtb2/auction.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,10 +382,13 @@ func sendAuctionResponse(
382382

383383
w.Header().Set("Content-Type", "application/json")
384384

385+
// Exitpoint will modify the response and set response headers according to hook implementation.
386+
finalResponse := hookExecutor.ExecuteExitpointStage(response, w)
387+
385388
// If an error happens when encoding the response, there isn't much we can do.
386389
// If we've sent _any_ bytes, then Go would have sent the 200 status code first.
387390
// That status code can't be un-sent... so the best we can do is log the error.
388-
if err := enc.Encode(response); err != nil {
391+
if err := enc.Encode(finalResponse); err != nil {
389392
labels.RequestStatus = metrics.RequestStatusNetworkErr
390393
ao.Errors = append(ao.Errors, fmt.Errorf("/openrtb2/auction Failed to send response: %v", err))
391394
}

endpoints/openrtb2/auction_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4915,6 +4915,11 @@ func TestValidResponseAfterExecutingStages(t *testing.T) {
49154915
file: "sample-requests/hooks/auction.json",
49164916
planBuilder: hooksPlanBuilder,
49174917
},
4918+
{
4919+
description: "Assert modified bidresponse after exitpoint hook implementation",
4920+
file: "sample-requests/hooks/auction_exitpoint.json",
4921+
planBuilder: mockPlanBuilder{exitpointPlan: makePlan[hookstage.Exitpoint](mockUpdateResponseHook{})},
4922+
},
49184923
}
49194924

49204925
for _, tc := range testCases {
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
{
2+
"description": "Amp Exitpoint",
3+
"query": "tag_id=101&trace=verbose",
4+
"config": {
5+
"mockBidders": [
6+
{
7+
"bidderName": "pubmatic",
8+
"currency": "USD",
9+
"price": 2
10+
}
11+
]
12+
},
13+
"mockBidRequest": {
14+
"id": "my-req-id",
15+
"site": {
16+
"page": "test.somepage.com"
17+
},
18+
"imp": [
19+
{
20+
"id": "my-imp-id",
21+
"banner": {
22+
"format": [
23+
{
24+
"w": 300,
25+
"h": 600
26+
}
27+
]
28+
},
29+
"ext": {
30+
"prebid": {
31+
"bidder": {
32+
"pubmatic": {
33+
"publisherId": 12345
34+
}
35+
}
36+
}
37+
}
38+
}
39+
],
40+
"ext": {
41+
"prebid": {
42+
"debug": true
43+
}
44+
}
45+
},
46+
"expectedAmpResponse": {
47+
"id": "modified-id"
48+
},
49+
"expectedReturnCode": 200
50+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{
2+
"description": "Auction Exitpoint",
3+
"config": {
4+
"mockBidders": [
5+
{
6+
"bidderName": "pubmatic",
7+
"currency": "USD",
8+
"price": 1.00
9+
}
10+
]
11+
},
12+
"mockBidRequest": {
13+
"id": "some-request-id",
14+
"site": {
15+
"page": "prebid.org"
16+
},
17+
"imp": [
18+
{
19+
"id": "some-impression-id",
20+
"banner": {
21+
"format": [
22+
{
23+
"w": 300,
24+
"h": 250
25+
}
26+
]
27+
},
28+
"ext": {
29+
"pubmatic": {
30+
"publisherId": 12345
31+
}
32+
}
33+
}
34+
],
35+
"tmax": 500,
36+
"test": 1,
37+
"ext": {
38+
"prebid": {
39+
"trace": "verbose"
40+
}
41+
}
42+
},
43+
"expectedBidResponse": {
44+
"id": "modified-id"
45+
},
46+
"expectedReturnCode": 200
47+
}

endpoints/openrtb2/test_utils.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1529,6 +1529,7 @@ type mockPlanBuilder struct {
15291529
rawBidderResponsePlan hooks.Plan[hookstage.RawBidderResponse]
15301530
allProcessedBidResponsesPlan hooks.Plan[hookstage.AllProcessedBidResponses]
15311531
auctionResponsePlan hooks.Plan[hookstage.AuctionResponse]
1532+
exitpointPlan hooks.Plan[hookstage.Exitpoint]
15321533
}
15331534

15341535
func (m mockPlanBuilder) PlanForEntrypointStage(_ string) hooks.Plan[hookstage.Entrypoint] {
@@ -1559,6 +1560,10 @@ func (m mockPlanBuilder) PlanForAuctionResponseStage(_ string, _ *config.Account
15591560
return m.auctionResponsePlan
15601561
}
15611562

1563+
func (m mockPlanBuilder) PlanForExitpointStage(_ string, _ *config.Account) hooks.Plan[hookstage.Exitpoint] {
1564+
return m.exitpointPlan
1565+
}
1566+
15621567
func makePlan[H any](hook H) hooks.Plan[H] {
15631568
return hooks.Plan[H]{
15641569
{
@@ -1742,3 +1747,20 @@ func (m mockUpdateHook) HandleRawAuctionHook(
17421747
) (hookstage.HookResult[hookstage.RawAuctionRequestPayload], error) {
17431748
return hookstage.HookResult[hookstage.RawAuctionRequestPayload]{}, nil
17441749
}
1750+
1751+
type mockUpdateResponseHook struct{}
1752+
1753+
func (e mockUpdateResponseHook) HandleExitpointHook(
1754+
_ context.Context,
1755+
_ hookstage.ModuleInvocationContext,
1756+
_ hookstage.ExitpointPaylaod) (hookstage.HookResult[hookstage.ExitpointPaylaod], error) {
1757+
c := hookstage.ChangeSet[hookstage.ExitpointPaylaod]{}
1758+
c.AddMutation(
1759+
func(payload hookstage.ExitpointPaylaod) (hookstage.ExitpointPaylaod, error) {
1760+
payload.Response = &openrtb2.BidResponse{ID: "modified-id"}
1761+
payload.W.Header().Set("Content-Type", "application/json")
1762+
return payload, nil
1763+
}, hookstage.MutationUpdate, "exitpoint", "bidResponse.json-response")
1764+
1765+
return hookstage.HookResult[hookstage.ExitpointPaylaod]{ChangeSet: c}, nil
1766+
}

hooks/empty_plan.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,7 @@ func (e EmptyPlanBuilder) PlanForAllProcessedBidResponsesStage(endpoint string,
3636
func (e EmptyPlanBuilder) PlanForAuctionResponseStage(endpoint string, account *config.Account) Plan[hookstage.AuctionResponse] {
3737
return nil
3838
}
39+
40+
func (e EmptyPlanBuilder) PlanForExitpointStage(endpoint string, account *config.Account) Plan[hookstage.Exitpoint] {
41+
return nil
42+
}

hooks/empty_plan_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,5 @@ func TestEmptyPlanBuilder(t *testing.T) {
1818
assert.Len(t, planBuilder.PlanForRawBidderResponseStage(endpoint, nil), 0, message, StageRawBidderResponse)
1919
assert.Len(t, planBuilder.PlanForAllProcessedBidResponsesStage(endpoint, nil), 0, message, StageAllProcessedBidResponses)
2020
assert.Len(t, planBuilder.PlanForAuctionResponseStage(endpoint, nil), 0, message, StageAuctionResponse)
21+
assert.Len(t, planBuilder.PlanForExitpointStage(endpoint, nil), 0, message, StageExitpoint)
2122
}

hooks/hookexecution/executor.go

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ const (
2929
entityAuctionRequest entity = "auction-request"
3030
entityAuctionResponse entity = "auction_response"
3131
entityAllProcessedBidResponses entity = "all_processed_bid_responses"
32+
entityExitpoint entity = "exitpoint"
3233
)
3334

3435
type StageExecutor interface {
@@ -39,6 +40,7 @@ type StageExecutor interface {
3940
ExecuteRawBidderResponseStage(response *adapters.BidderResponse, bidder string) *RejectError
4041
ExecuteAllProcessedBidResponsesStage(adapterBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid)
4142
ExecuteAuctionResponseStage(response *openrtb2.BidResponse)
43+
ExecuteExitpointStage(response any, w http.ResponseWriter) any
4244
}
4345

4446
type HookStageExecutor interface {
@@ -295,6 +297,35 @@ func (e *hookExecutor) ExecuteAuctionResponseStage(response *openrtb2.BidRespons
295297
e.pushStageOutcome(outcome)
296298
}
297299

300+
func (e *hookExecutor) ExecuteExitpointStage(response any, w http.ResponseWriter) any {
301+
plan := e.planBuilder.PlanForExitpointStage(e.endpoint, e.account)
302+
if len(plan) == 0 {
303+
return response
304+
}
305+
306+
handler := func(
307+
ctx context.Context,
308+
moduleCtx hookstage.ModuleInvocationContext,
309+
hook hookstage.Exitpoint,
310+
payload hookstage.ExitpointPaylaod,
311+
) (hookstage.HookResult[hookstage.ExitpointPaylaod], error) {
312+
return hook.HandleExitpointHook(ctx, moduleCtx, payload)
313+
}
314+
315+
stageName := hooks.StageExitpoint.String()
316+
executionCtx := e.newContext(stageName)
317+
payload := hookstage.ExitpointPaylaod{W: w, Response: response}
318+
319+
outcome, payload, context, _ := executeStage(executionCtx, plan, payload, handler, e.metricEngine)
320+
outcome.Entity = entityExitpoint
321+
outcome.Stage = stageName
322+
323+
e.saveModuleContexts(context)
324+
e.pushStageOutcome(outcome)
325+
326+
return payload.Response
327+
}
328+
298329
func (e *hookExecutor) newContext(stage string) executionContext {
299330
return executionContext{
300331
account: e.account,
@@ -353,4 +384,9 @@ func (executor EmptyHookExecutor) ExecuteRawBidderResponseStage(_ *adapters.Bidd
353384
func (executor EmptyHookExecutor) ExecuteAllProcessedBidResponsesStage(_ map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid) {
354385
}
355386

356-
func (executor EmptyHookExecutor) ExecuteAuctionResponseStage(_ *openrtb2.BidResponse) {}
387+
func (executor EmptyHookExecutor) ExecuteAuctionResponseStage(_ *openrtb2.BidResponse) {
388+
}
389+
390+
func (executor EmptyHookExecutor) ExecuteExitpointStage(response any, _ http.ResponseWriter) any {
391+
return response
392+
}

0 commit comments

Comments
 (0)