Skip to content

Commit

Permalink
Core: Update NextMillennium bidder (#3103)
Browse files Browse the repository at this point in the history
  • Loading branch information
marki1an authored May 22, 2024
1 parent 37cd7aa commit f10fbe4
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.iab.openrtb.request.Format;
import com.iab.openrtb.request.Imp;
import com.iab.openrtb.request.Site;
import com.iab.openrtb.response.Bid;
import com.iab.openrtb.response.BidResponse;
import com.iab.openrtb.response.SeatBid;
import io.vertx.core.MultiMap;
Expand All @@ -21,6 +22,7 @@
import org.prebid.server.bidder.model.BidderError;
import org.prebid.server.bidder.model.HttpRequest;
import org.prebid.server.bidder.model.Result;
import org.prebid.server.exception.PreBidException;
import org.prebid.server.json.DecodeException;
import org.prebid.server.json.JacksonMapper;
import org.prebid.server.proto.openrtb.ext.ExtPrebid;
Expand All @@ -36,6 +38,7 @@
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

public class NextMillenniumBidder implements Bidder<BidRequest> {

Expand All @@ -57,47 +60,53 @@ public NextMillenniumBidder(String endpointUrl, JacksonMapper mapper, List<Strin
@Override
public final Result<List<HttpRequest<BidRequest>>> makeHttpRequests(BidRequest bidRequest) {
final List<BidderError> errors = new ArrayList<>();
final List<ExtImpNextMillennium> impExts = getImpExts(bidRequest, errors);

return errors.isEmpty()
? Result.withValues(makeRequests(bidRequest, impExts))
: Result.withErrors(errors);
}

private List<ExtImpNextMillennium> getImpExts(BidRequest bidRequest, List<BidderError> errors) {
return bidRequest.getImp().stream()
.map(imp -> convertExt(imp, errors))
.toList();
}
final List<HttpRequest<BidRequest>> httpRequests = new ArrayList<>();

for (Imp imp : bidRequest.getImp()) {
final ExtImpNextMillennium extImpNextMillennium;
try {
extImpNextMillennium = convertExt(imp.getExt());
} catch (PreBidException e) {
errors.add(BidderError.badInput(e.getMessage()));
continue;
}
httpRequests.add(makeHttpRequest(updateBidRequest(bidRequest, extImpNextMillennium)));
}

private List<HttpRequest<BidRequest>> makeRequests(BidRequest bidRequest, List<ExtImpNextMillennium> extImps) {
return extImps.stream()
.map(extImp -> makeHttpRequest(updateBidRequest(bidRequest, extImp)))
.toList();
return errors.isEmpty() ? Result.withValues(httpRequests) : Result.withErrors(errors);
}

private ExtImpNextMillennium convertExt(Imp imp, List<BidderError> errors) {
private ExtImpNextMillennium convertExt(ObjectNode impExt) {
try {
return mapper.mapper()
.convertValue(imp.getExt(), NEXTMILLENNIUM_EXT_TYPE_REFERENCE)
.getBidder();
return mapper.mapper().convertValue(impExt, NEXTMILLENNIUM_EXT_TYPE_REFERENCE).getBidder();
} catch (IllegalArgumentException e) {
errors.add(BidderError.badInput(e.getMessage()));
throw new PreBidException(e.getMessage());
}
return null;
}

private BidRequest updateBidRequest(BidRequest bidRequest, ExtImpNextMillennium ext) {
final ExtRequestPrebid prebid = ExtRequestPrebid.builder()
.storedrequest(ExtStoredRequest.of(resolveStoredRequestId(bidRequest, ext)))
final ExtStoredRequest storedRequest = ExtStoredRequest.of(resolveStoredRequestId(bidRequest, ext));

final ExtRequestPrebid createdExtRequestPrebid = ExtRequestPrebid.builder()
.storedrequest(storedRequest)
.build();
final ExtRequest extRequest = ExtRequest.of(prebid);

final List<Imp> imps = bidRequest.getImp().stream()
.map(imp -> imp.toBuilder().ext(createImpExt(prebid)).build())
.toList();
final ExtRequestPrebid extRequestPrebid = Optional.ofNullable(bidRequest)
.map(BidRequest::getExt)
.map(ExtRequest::getPrebid)
.map(prebid -> prebid.toBuilder().storedrequest(storedRequest).build())
.orElse(createdExtRequestPrebid);

return bidRequest.toBuilder()
.imp(updateImps(bidRequest, createdExtRequestPrebid))
.ext(ExtRequest.of(extRequestPrebid))
.build();
}

return bidRequest.toBuilder().imp(imps).ext(extRequest).build();
private List<Imp> updateImps(BidRequest bidRequest, ExtRequestPrebid extRequestPrebid) {
return bidRequest.getImp().stream()
.map(imp -> imp.toBuilder().ext(createImpExt(extRequestPrebid)).build())
.toList();
}

private static String resolveStoredRequestId(BidRequest bidRequest, ExtImpNextMillennium extImpNextMillennium) {
Expand Down Expand Up @@ -164,24 +173,53 @@ private static MultiMap headers() {

@Override
public final Result<List<BidderBid>> makeBids(BidderCall<BidRequest> httpCall, BidRequest bidRequest) {
final List<BidderError> bidderErrors = new ArrayList<>();
try {
final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class);
if (CollectionUtils.isEmpty(bidResponse.getSeatbid())) {
return Result.empty();
}
return Result.withValues(bidsFromResponse(bidResponse));
return Result.of(bidsFromResponse(bidResponse, bidderErrors), bidderErrors);
} catch (DecodeException e) {
return Result.withError(BidderError.badServerResponse(e.getMessage()));
}
}

private static List<BidderBid> bidsFromResponse(BidResponse bidResponse) {
private static List<BidderBid> bidsFromResponse(BidResponse bidResponse, List<BidderError> bidderErrors) {
return bidResponse.getSeatbid().stream()
.filter(Objects::nonNull)
.map(SeatBid::getBid)
.filter(Objects::nonNull)
.flatMap(Collection::stream)
.map(bid -> BidderBid.of(bid, BidType.banner, bidResponse.getCur()))
.map(bid -> resolveBidderBid(bidResponse, bidderErrors, bid))
.filter(Objects::nonNull)
.toList();
}

private static BidderBid resolveBidderBid(BidResponse bidResponse, List<BidderError> bidderErrors, Bid bid) {
final BidType bidType = getBidType(bid, bidderErrors);
if (bidType == null) {
return null;
}

return BidderBid.of(bid, bidType, bidResponse.getCur());
}

private static BidType getBidType(Bid bid, List<BidderError> bidderErrors) {
final Integer markupType = bid.getMtype();
if (markupType == null) {
bidderErrors.add(BidderError.badServerResponse("Missing MType for bid: " + bid.getId()));
return null;
}

return switch (markupType) {
case 1 -> BidType.banner;
case 2 -> BidType.video;
default -> {
bidderErrors.add(BidderError.badServerResponse(
"Unable to fetch mediaType " + bid.getMtype() + " in multi-format: " + bid.getImpid()));
yield null;
}
};
}
}
2 changes: 2 additions & 0 deletions src/main/resources/bidder-config/nextmillennium.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ adapters:
maintainer-email: [email protected]
app-media-types:
- banner
- video
site-media-types:
- banner
- video
supported-vendors:
vendor-id: 1060
usersync:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.prebid.server.proto.openrtb.ext.request.nextmillennium.ExtImpNextMillennium;
import org.prebid.server.proto.openrtb.ext.response.BidType;

import java.util.Arrays;
import java.util.List;
import java.util.function.UnaryOperator;

Expand Down Expand Up @@ -275,19 +276,88 @@ public void makeHttpRequestsShouldReturnImpExtNextMillenniumWhenNmmFlagsConfigur
}

@Test
public void makeBidsShouldReturnBannerBidByDefault() throws JsonProcessingException {
public void makeBidsShouldReturnBannerBidWhenMTypeIsOne() throws JsonProcessingException {
// given
final BidRequest bidRequest = givenBidRequest(identity());
final BidderCall<BidRequest> httpCall = givenHttpCall(bidRequest,
mapper.writeValueAsString(givenBidResponse(bidBuilder -> bidBuilder.impid("123"))));
mapper.writeValueAsString(givenBidResponse(bidBuilder -> bidBuilder.mtype(1).impid("123"))));

// when
final Result<List<BidderBid>> result = target.makeBids(httpCall, bidRequest);

// then
assertThat(result.getErrors()).isEmpty();
assertThat(result.getValue())
.containsExactly(BidderBid.of(Bid.builder().impid("123").build(), BidType.banner, "USD"));
.containsExactly(BidderBid.of(Bid.builder().mtype(1).impid("123").build(),
BidType.banner, "USD"));
}

@Test
public void makeBidsShouldReturnVideoBidWhenMTypeIsTwo() throws JsonProcessingException {
// given
final BidRequest bidRequest = givenBidRequest(identity());
final BidderCall<BidRequest> httpCall = givenHttpCall(bidRequest,
mapper.writeValueAsString(givenBidResponse(bidBuilder -> bidBuilder.mtype(2).impid("123"))));

// when
final Result<List<BidderBid>> result = target.makeBids(httpCall, bidRequest);

// then
assertThat(result.getErrors()).isEmpty();
assertThat(result.getValue())
.containsExactly(BidderBid.of(Bid.builder().mtype(2).impid("123").build(),
BidType.video, "USD"));
}

@Test
public void makeBidsShouldReturnErrorWhenMTypeIsUnknown() throws JsonProcessingException {
// given
final BidRequest bidRequest = givenBidRequest(identity());
final BidderCall<BidRequest> httpCall = givenHttpCall(bidRequest,
mapper.writeValueAsString(givenBidResponse(bidBuilder -> bidBuilder.mtype(999).impid("123"))));

// when
final Result<List<BidderBid>> result = target.makeBids(httpCall, bidRequest);

// then
assertThat(result.getErrors())
.contains(BidderError.badServerResponse("Unable to fetch mediaType 999 in multi-format: 123"));
assertThat(result.getValue()).isEmpty();
}

@Test
public void makeBidsShouldReturnErrorWhenMTypeIsMissing() throws JsonProcessingException {
// given
final BidRequest bidRequest = givenBidRequest(identity());
final BidderCall<BidRequest> httpCall = givenHttpCall(bidRequest,
mapper.writeValueAsString(givenBidResponse(bidBuilder -> bidBuilder.id("bidId").impid("123"))));

// when
final Result<List<BidderBid>> result = target.makeBids(httpCall, bidRequest);

// then
assertThat(result.getErrors()).contains(BidderError.badServerResponse("Missing MType for bid: bidId"));
assertThat(result.getValue()).isEmpty();
}

@Test
public void makeBidsShouldReturnBothValidAndInvalidBidderBidAtTheSameTime() throws JsonProcessingException {
// given
final BidRequest bidRequest = givenBidRequest(identity());
final BidderCall<BidRequest> httpCall = givenHttpCall(givenBidRequest(identity()),
mapper.writeValueAsString(givenBidResponse(
bidBuilder -> bidBuilder.mtype(999).impid("123"),
bidBuilder -> bidBuilder.mtype(1).impid("312"))));

// when
final Result<List<BidderBid>> result = target.makeBids(httpCall, bidRequest);

// then
assertThat(result.getErrors())
.contains(BidderError.badServerResponse("Unable to fetch mediaType 999 in multi-format: 123"));
assertThat(result.getValue())
.containsExactly(BidderBid.of(Bid.builder().mtype(1).impid("312").build(),
BidType.banner, "USD"));
}

@Test
Expand Down Expand Up @@ -476,12 +546,15 @@ private static Imp givenImp(UnaryOperator<Imp.ImpBuilder> impCustomizer) {
return impCustomizer.apply(Imp.builder()).build();
}

private static BidResponse givenBidResponse(UnaryOperator<Bid.BidBuilder> bidCustomizer) {
@SafeVarargs
private static BidResponse givenBidResponse(UnaryOperator<Bid.BidBuilder>... bidCustomizers) {
return BidResponse.builder()
.cur("USD")
.seatbid(singletonList(SeatBid.builder()
.bid(singletonList(bidCustomizer.apply(Bid.builder()).build()))
.bid(Arrays.stream(bidCustomizers)
.map(bidCustomizer -> bidCustomizer.apply(Bid.builder()).build())
.toList())
.build()))
.cur("USD")
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"id": "bid_id",
"impid": "imp_id",
"price": 0.01,
"mtype": 1,
"adid": "adid001",
"cid": "cid001",
"crid": "crid001",
Expand All @@ -25,7 +26,7 @@
"cur": "USD",
"ext": {
"responsetimemillis": {
"nextmillennium" : "{{ nextmillennium.response_time_ms }}"
"nextmillennium": "{{ nextmillennium.response_time_ms }}"
},
"prebid": {
"auctiontimestamp": 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@
"prebid": {
"storedrequest": {
"id": "placement_id"
},
"server": {
"externalurl": "http://localhost:8080",
"gvlid": 1,
"datacenter": "local",
"endpoint": "/openrtb2/auction"
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
{
"crid": "crid001",
"adid": "adid001",
"mtype": 1,
"price": 0.01,
"id": "bid_id",
"impid": "imp_id",
Expand Down

0 comments on commit f10fbe4

Please sign in to comment.