Skip to content

Commit d87670e

Browse files
OMS: Video support #3770
1 parent 474ad5c commit d87670e

File tree

7 files changed

+208
-24
lines changed

7 files changed

+208
-24
lines changed

src/main/java/org/prebid/server/bidder/oms/OmsBidder.java

+44-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package org.prebid.server.bidder.oms;
22

3+
import com.fasterxml.jackson.core.type.TypeReference;
34
import com.iab.openrtb.request.BidRequest;
5+
import com.iab.openrtb.request.Imp;
46
import com.iab.openrtb.response.BidResponse;
57
import com.iab.openrtb.response.SeatBid;
68
import org.apache.commons.collections4.CollectionUtils;
@@ -10,8 +12,11 @@
1012
import org.prebid.server.bidder.model.BidderError;
1113
import org.prebid.server.bidder.model.HttpRequest;
1214
import org.prebid.server.bidder.model.Result;
15+
import org.prebid.server.exception.PreBidException;
1316
import org.prebid.server.json.DecodeException;
1417
import org.prebid.server.json.JacksonMapper;
18+
import org.prebid.server.proto.openrtb.ext.ExtPrebid;
19+
import org.prebid.server.proto.openrtb.ext.request.omx.ExtImpOms;
1520
import org.prebid.server.proto.openrtb.ext.response.BidType;
1621
import org.prebid.server.util.BidderUtil;
1722
import org.prebid.server.util.HttpUtil;
@@ -23,6 +28,8 @@
2328

2429
public class OmsBidder implements Bidder<BidRequest> {
2530

31+
private static final TypeReference<ExtPrebid<?, ExtImpOms>> EXT_TYPE_REFERENCE = new TypeReference<>() {
32+
};
2633
private final String endpointUrl;
2734
private final JacksonMapper mapper;
2835

@@ -32,16 +39,41 @@ public OmsBidder(String endpointUrl, JacksonMapper mapper) {
3239
}
3340

3441
@Override
35-
public final Result<List<HttpRequest<BidRequest>>> makeHttpRequests(BidRequest bidRequest) {
36-
return Result.withValue(BidderUtil.defaultRequest(bidRequest, endpointUrl, mapper));
42+
public Result<List<HttpRequest<BidRequest>>> makeHttpRequests(BidRequest request) {
43+
String uri = endpointUrl;
44+
45+
if (!request.getImp().isEmpty()) {
46+
try {
47+
final ExtImpOms impExt = parseImpExt(request.getImp().get(0));
48+
if (impExt != null) {
49+
if (impExt.getPid() != null && !impExt.getPid().isEmpty()) {
50+
uri = String.format("%s?publisherId=%s", endpointUrl, impExt.getPid());
51+
} else if (impExt.getPublisherId() != null && impExt.getPublisherId() > 0) {
52+
uri = String.format("%s?publisherId=%d", endpointUrl, impExt.getPublisherId());
53+
}
54+
}
55+
} catch (PreBidException e) {
56+
return Result.withError(BidderError.badInput(e.getMessage()));
57+
}
58+
}
59+
60+
return Result.withValue(BidderUtil.defaultRequest(request, uri, mapper));
61+
}
62+
63+
private ExtImpOms parseImpExt(Imp imp) throws PreBidException {
64+
try {
65+
return mapper.mapper().convertValue(imp.getExt(), EXT_TYPE_REFERENCE).getBidder();
66+
} catch (IllegalArgumentException e) {
67+
throw new PreBidException("Invalid ext. Imp.Id: " + imp.getId());
68+
}
3769
}
3870

3971
@Override
4072
public final Result<List<BidderBid>> makeBids(BidderCall<BidRequest> httpCall, BidRequest bidRequest) {
4173
try {
4274
final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class);
4375
return Result.withValues(extractBids(bidResponse));
44-
} catch (DecodeException e) {
76+
} catch (DecodeException | PreBidException e) {
4577
return Result.withError(BidderError.badServerResponse(e.getMessage()));
4678
}
4779
}
@@ -59,7 +91,15 @@ private static List<BidderBid> bidsFromResponse(BidResponse bidResponse) {
5991
.map(SeatBid::getBid)
6092
.filter(Objects::nonNull)
6193
.flatMap(Collection::stream)
62-
.map(bid -> BidderBid.of(bid, BidType.banner, bidResponse.getCur()))
94+
.map(bid -> BidderBid.of(bid, getBidType(bid.getMtype()), bidResponse.getCur()))
6395
.toList();
6496
}
97+
98+
private static BidType getBidType(Integer mType) {
99+
return switch (mType) {
100+
case 1 -> BidType.banner;
101+
case 2 -> BidType.video;
102+
case null, default -> throw new PreBidException("Unsupported mType " + mType);
103+
};
104+
}
65105
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package org.prebid.server.proto.openrtb.ext.request.omx;
2+
3+
import lombok.Value;
4+
5+
@Value(staticConstructor = "of")
6+
public class ExtImpOms {
7+
8+
String pid;
9+
10+
Integer publisherId;
11+
}

src/main/resources/bidder-config/oms.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ adapters:
55
maintainer-email: [email protected]
66
app-media-types:
77
- banner
8+
- video
89
site-media-types:
910
- banner
11+
- video
1012
supported-vendors:
1113
vendor-id: 0

src/main/resources/static/bidder-params/oms.json

+17-3
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,25 @@
66
"properties": {
77
"pid": {
88
"type": "string",
9-
"description": "An id used to identify OMS publisher.",
9+
"description": "Deprecated: An id used to identify OMS publisher.",
1010
"minLength": 5
11+
},
12+
"publisherId": {
13+
"type": "integer",
14+
"description": "An ID used to identify OMS publisher.",
15+
"minimum": 10000
1116
}
1217
},
13-
"required": [
14-
"pid"
18+
"oneOf": [
19+
{
20+
"required": [
21+
"pid"
22+
]
23+
},
24+
{
25+
"required": [
26+
"publisherId"
27+
]
28+
}
1529
]
1630
}

src/test/java/org/prebid/server/bidder/oms/OmsBidderTest.java

+131-16
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.prebid.server.bidder.oms;
22

33
import com.fasterxml.jackson.core.JsonProcessingException;
4+
import com.fasterxml.jackson.databind.node.ObjectNode;
45
import com.iab.openrtb.request.Banner;
56
import com.iab.openrtb.request.BidRequest;
67
import com.iab.openrtb.request.Imp;
@@ -15,7 +16,12 @@
1516
import org.prebid.server.bidder.model.HttpRequest;
1617
import org.prebid.server.bidder.model.HttpResponse;
1718
import org.prebid.server.bidder.model.Result;
19+
import org.prebid.server.proto.openrtb.ext.ExtPrebid;
20+
import org.prebid.server.proto.openrtb.ext.request.omx.ExtImpOms;
21+
import org.prebid.server.proto.openrtb.ext.response.BidType;
1822

23+
import java.util.Arrays;
24+
import java.util.Collections;
1925
import java.util.List;
2026
import java.util.function.Function;
2127
import java.util.function.UnaryOperator;
@@ -24,7 +30,7 @@
2430
import static java.util.function.UnaryOperator.identity;
2531
import static org.assertj.core.api.Assertions.assertThat;
2632
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
27-
import static org.prebid.server.proto.openrtb.ext.response.BidType.banner;
33+
import static org.prebid.server.bidder.model.BidderError.badInput;
2834

2935
public class OmsBidderTest extends VertxTest {
3036

@@ -37,10 +43,40 @@ public void creationShouldFailOnInvalidEndpointUrl() {
3743
assertThatIllegalArgumentException().isThrownBy(() -> new OmsBidder("invalid_url", jacksonMapper));
3844
}
3945

46+
@Test
47+
public void makeHttpRequestsShouldReturnErrorWhenRequestHasInvalidImpression() {
48+
// given
49+
final ObjectNode invalidExt = mapper.valueToTree(ExtPrebid.of(null, mapper.createArrayNode()));
50+
final BidRequest bidRequest = givenBidRequest(impBuilder -> impBuilder.ext(invalidExt));
51+
52+
// when
53+
final Result<List<HttpRequest<BidRequest>>> result = target.makeHttpRequests(bidRequest);
54+
55+
// then
56+
assertThat(result.getErrors()).hasSize(1).first().isEqualTo(badInput("Invalid ext. Imp.Id: 123"));
57+
}
58+
4059
@Test
4160
public void makeHttpRequestsShouldCreateExpectedUrl() {
4261
// given
43-
final BidRequest bidRequest = givenBidRequest(identity());
62+
final ExtImpOms impExt = ExtImpOms.of("otherTagId", 12345);
63+
final BidRequest bidRequest = givenBidRequest(impCustomizer -> impCustomizer.ext(givenImpExt(impExt)));
64+
65+
// when
66+
final Result<List<HttpRequest<BidRequest>>> result = target.makeHttpRequests(bidRequest);
67+
68+
// then
69+
assertThat(result.getErrors()).isEmpty();
70+
assertThat(result.getValue()).hasSize(1)
71+
.extracting(HttpRequest::getUri)
72+
.containsExactly("https://randomurl.com?publisherId=otherTagId");
73+
}
74+
75+
@Test
76+
public void makeHttpRequestsShouldCreateExpectedUrlWithPublisherId() {
77+
// given
78+
final ExtImpOms impExt = ExtImpOms.of(null, 12345);
79+
final BidRequest bidRequest = givenBidRequest(impCustomizer -> impCustomizer.ext(givenImpExt(impExt)));
4480

4581
// when
4682
final Result<List<HttpRequest<BidRequest>>> result = target.makeHttpRequests(bidRequest);
@@ -49,7 +85,43 @@ public void makeHttpRequestsShouldCreateExpectedUrl() {
4985
assertThat(result.getErrors()).isEmpty();
5086
assertThat(result.getValue()).hasSize(1)
5187
.extracting(HttpRequest::getUri)
52-
.containsExactly("https://randomurl.com");
88+
.containsExactly("https://randomurl.com?publisherId=12345");
89+
}
90+
91+
@Test
92+
public void makeHttpRequestsShouldIncludePidInRequestWhenPresent() {
93+
// given
94+
final ObjectNode impExt = mapper.createObjectNode().put("pid", "examplePid");
95+
final BidRequest bidRequest = givenBidRequest(impBuilder -> impBuilder.ext(impExt));
96+
97+
// when
98+
final Result<List<HttpRequest<BidRequest>>> result = target.makeHttpRequests(bidRequest);
99+
100+
// then
101+
assertThat(result.getErrors()).isEmpty();
102+
assertThat(result.getValue())
103+
.extracting(HttpRequest::getPayload)
104+
.flatExtracting(BidRequest::getImp)
105+
.extracting(Imp::getExt)
106+
.containsExactly(impExt);
107+
}
108+
109+
@Test
110+
public void makeHttpRequestsShouldIncludePublisherIdInRequestWhenPresent() {
111+
// given
112+
final ObjectNode impExt = mapper.createObjectNode().put("publisherId", 12345);
113+
final BidRequest bidRequest = givenBidRequest(impBuilder -> impBuilder.ext(impExt));
114+
115+
// when
116+
final Result<List<HttpRequest<BidRequest>>> result = target.makeHttpRequests(bidRequest);
117+
118+
// then
119+
assertThat(result.getErrors()).isEmpty();
120+
assertThat(result.getValue())
121+
.extracting(HttpRequest::getPayload)
122+
.flatExtracting(BidRequest::getImp)
123+
.extracting(Imp::getExt)
124+
.containsExactly(impExt);
53125
}
54126

55127
@Test
@@ -101,32 +173,75 @@ public void makeBidsShouldReturnBannerBid() throws JsonProcessingException {
101173
// given
102174
final BidderCall<BidRequest> httpCall = givenHttpCall(
103175
givenBidRequest(impBuilder -> impBuilder.banner(Banner.builder().build())),
104-
mapper.writeValueAsString(givenBidResponse(impBuilder -> impBuilder.impid("123"))));
176+
mapper.writeValueAsString(givenBidResponse(impBuilder -> impBuilder.impid("123").mtype(1))));
105177

106178
// when
107179
final Result<List<BidderBid>> result = target.makeBids(httpCall, null);
108180

109181
// then
110182
assertThat(result.getErrors()).isEmpty();
111-
assertThat(result.getValue())
112-
.containsExactly(BidderBid.of(givenBid(), banner, null));
183+
assertThat(result.getValue()).extracting(BidderBid::getType).containsExactly(BidType.banner);
113184
}
114185

115186
@Test
116-
public void makeBidsShouldReturnBannerBidIfBannerAndVideoAndAudioAndNativeIsAbsentInRequestImp()
117-
throws JsonProcessingException {
187+
public void makeBidsShouldReturnVideoBid() throws JsonProcessingException {
118188
// given
119189
final BidderCall<BidRequest> httpCall = givenHttpCall(
120-
givenBidRequest(identity()),
121-
mapper.writeValueAsString(givenBidResponse(impBuilder -> impBuilder.impid("123"))));
190+
givenBidRequest(impBuilder -> impBuilder.banner(Banner.builder().build())),
191+
mapper.writeValueAsString(givenBidResponse(impBuilder -> impBuilder.impid("123").mtype(2))));
122192

123193
// when
124194
final Result<List<BidderBid>> result = target.makeBids(httpCall, null);
125195

126196
// then
127197
assertThat(result.getErrors()).isEmpty();
128-
assertThat(result.getValue())
129-
.containsExactly(BidderBid.of(givenBid(), banner, null));
198+
assertThat(result.getValue()).extracting(BidderBid::getType).containsExactly(BidType.video);
199+
}
200+
201+
@Test
202+
public void makeBidsShouldReturnErrorWhenMTypeIsUnsupported() throws JsonProcessingException {
203+
// given
204+
final BidderCall<BidRequest> httpCall = givenHttpCall(
205+
givenBidRequest(impBuilder -> impBuilder.banner(Banner.builder().build())),
206+
mapper.writeValueAsString(givenBidResponse(impBuilder -> impBuilder.impid("123").mtype(99))));
207+
208+
// when
209+
final Result<List<BidderBid>> result = target.makeBids(httpCall, null);
210+
211+
// then
212+
assertThat(result.getErrors()).hasSize(1);
213+
assertThat(result.getErrors().get(0).getMessage()).contains("Unsupported mType 99");
214+
assertThat(result.getValue()).isEmpty();
215+
}
216+
217+
@Test
218+
public void makeBidsShouldExtractAllBidsFromMultipleSeatBids() throws JsonProcessingException {
219+
// given
220+
final Bid bid1 = Bid.builder().impid("bid1").mtype(1).build();
221+
final Bid bid2 = Bid.builder().impid("bid2").mtype(1).build();
222+
final Bid bid3 = Bid.builder().impid("bid3").mtype(2).build();
223+
224+
final SeatBid seatBid1 = SeatBid.builder().bid(Arrays.asList(bid1, bid2)).build();
225+
final SeatBid seatBid2 = SeatBid.builder().bid(Collections.singletonList(bid3)).build();
226+
227+
final BidResponse bidResponse = BidResponse.builder()
228+
.seatbid(Arrays.asList(seatBid1, seatBid2))
229+
.cur("USD")
230+
.build();
231+
final String bidResponseJson = mapper.writeValueAsString(bidResponse);
232+
233+
final BidRequest bidRequest = givenBidRequest(impBuilder -> impBuilder.banner(Banner.builder().build()));
234+
final BidderCall<BidRequest> httpCall = givenHttpCall(bidRequest, bidResponseJson);
235+
236+
// when
237+
final Result<List<BidderBid>> result = target.makeBids(httpCall, bidRequest);
238+
239+
// then
240+
assertThat(result.getErrors()).isEmpty();
241+
assertThat(result.getValue()).hasSize(3)
242+
.extracting(BidderBid::getType)
243+
.containsExactly(BidType.banner, BidType.banner, BidType.video);
244+
assertThat(result.getValue()).extracting(BidderBid::getBidCurrency).containsOnly("USD");
130245
}
131246

132247
private static BidRequest givenBidRequest(UnaryOperator<Imp.ImpBuilder> impCustomizer) {
@@ -148,14 +263,14 @@ private static BidResponse givenBidResponse(Function<Bid.BidBuilder, Bid.BidBuil
148263
.build();
149264
}
150265

151-
private static Bid givenBid() {
152-
return Bid.builder().impid("123").build();
153-
}
154-
155266
private static BidderCall<BidRequest> givenHttpCall(BidRequest bidRequest, String body) {
156267
return BidderCall.succeededHttp(
157268
HttpRequest.<BidRequest>builder().payload(bidRequest).build(),
158269
HttpResponse.of(200, null, body),
159270
null);
160271
}
272+
273+
private ObjectNode givenImpExt(ExtImpOms impExt) {
274+
return mapper.valueToTree(ExtPrebid.of(null, impExt));
275+
}
161276
}

src/test/resources/org/prebid/server/it/openrtb2/oms/test-auction-oms-response.json

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"exp": 300,
1010
"price": 3.33,
1111
"crid": "creativeId",
12+
"mtype": 1,
1213
"ext": {
1314
"origbidcpm": 3.33,
1415
"prebid": {

src/test/resources/org/prebid/server/it/openrtb2/oms/test-oms-bid-response.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
"id": "bid_id",
88
"impid": "imp_id",
99
"price": 3.33,
10-
"crid": "creativeId"
10+
"crid": "creativeId",
11+
"mtype": 1
1112
}
1213
]
1314
}

0 commit comments

Comments
 (0)