Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MediaGrid Bidder: Native Support #3312

Merged
merged 1 commit into from
Jul 23, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
59 changes: 51 additions & 8 deletions src/main/java/org/prebid/server/bidder/grid/GridBidder.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.iab.openrtb.request.BidRequest;
import com.iab.openrtb.request.Imp;
import com.iab.openrtb.request.Native;
import com.iab.openrtb.request.Site;
import com.iab.openrtb.request.User;
import com.iab.openrtb.response.Bid;
Expand All @@ -16,6 +17,7 @@
import org.prebid.server.bidder.grid.model.request.ExtImp;
import org.prebid.server.bidder.grid.model.request.ExtImpGridData;
import org.prebid.server.bidder.grid.model.request.ExtImpGridDataAdServer;
import org.prebid.server.bidder.grid.model.request.GridNative;
import org.prebid.server.bidder.grid.model.request.Keywords;
import org.prebid.server.bidder.grid.model.response.GridBidResponse;
import org.prebid.server.bidder.grid.model.response.GridSeatBid;
Expand Down Expand Up @@ -103,19 +105,48 @@ private static void validateImpExt(ExtImp extImp, String impId) {
}

private Imp modifyImp(Imp imp, ExtImp extImp) {
return imp.toBuilder()
.xNative(modifyNative(imp.getXNative()))
.ext(modifyImpExt(extImp))
.build();
}

private ObjectNode modifyImpExt(ExtImp extImp) {
final ExtImpGridData extImpData = extImp.getData();
final ExtImpGridDataAdServer adServer = extImpData != null ? extImpData.getAdServer() : null;
final String adSlot = adServer != null ? adServer.getAdSlot() : null;

if (StringUtils.isNotEmpty(adSlot)) {
final ExtImp modifiedExtImp = extImp.toBuilder()
.gpid(adSlot)
.build();
return imp.toBuilder()
.ext(mapper.mapper().valueToTree(modifiedExtImp))
.build();
final ExtImp modifiedExtImp = extImp.toBuilder().gpid(adSlot).build();
return mapper.mapper().valueToTree(modifiedExtImp);
}

return mapper.mapper().valueToTree(extImp);
}

private Native modifyNative(Native xNative) {
if (xNative == null) {
return null;
}

final String nativeRequest = xNative.getRequest();
final JsonNode requestNode = nodeFromString(nativeRequest);

return GridNative.builder()
.requestNative((ObjectNode) requestNode)
.ver(xNative.getVer())
.api(xNative.getApi())
.battr(xNative.getBattr())
.ext(xNative.getExt())
.build();
}

public final JsonNode nodeFromString(String stringValue) {
try {
return StringUtils.isNotBlank(stringValue) ? mapper.mapper().readTree(stringValue) : null;
} catch (Exception e) {
return null;
}
return imp;
}

private Keywords getKeywordsFromImpExt(JsonNode extImp) {
Expand Down Expand Up @@ -218,14 +249,24 @@ private List<BidderBid> bidsFromResponse(BidRequest bidRequest, GridBidResponse
private BidderBid makeBidderBid(ObjectNode bidNode, List<Imp> imps, String currency) {
try {
final Bid bid = mapper.mapper().treeToValue(bidNode, Bid.class);
final Bid modifiedBid = bid.toBuilder().ext(modifyBidExt(bidNode)).build();
final Bid modifiedBid = bid.toBuilder().adm(modifyAdm(bidNode, bid)).ext(modifyBidExt(bidNode)).build();

return BidderBid.of(modifiedBid, resolveBidType(bidNode, bid.getImpid(), imps), currency);
} catch (JsonProcessingException | IllegalArgumentException e) {
throw new PreBidException(e.getMessage());
}
}

private String modifyAdm(ObjectNode bidNode, Bid bid) {
final JsonNode admNative = bidNode.at("/adm_native");
final String bidAdm = bid.getAdm();
if (admNative != null && !admNative.isEmpty() && StringUtils.isBlank(bidAdm)) {
return mapper.encodeToString(admNative);
}

return bidAdm;
}

private ObjectNode modifyBidExt(ObjectNode gridBid) {
final String demandSource = ObjectUtils.defaultIfNull(gridBid, MissingNode.getInstance())
.at("/ext/bidder/grid/demandSource")
Expand All @@ -252,6 +293,8 @@ private static BidType resolveBidType(ObjectNode bidNode, String impId, List<Imp
return BidType.banner;
} else if (imp.getVideo() != null) {
return BidType.video;
} else if (imp.getXNative() != null) {
return BidType.xNative;
}
throw new PreBidException("Unknown impression type for ID: " + impId);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.prebid.server.bidder.grid.model.request;

import com.fasterxml.jackson.databind.node.ObjectNode;
import com.iab.openrtb.request.Native;
import lombok.EqualsAndHashCode;
import lombok.Value;
import lombok.experimental.SuperBuilder;

@Value
@SuperBuilder
@EqualsAndHashCode(callSuper = true)
public class GridNative extends Native {

ObjectNode requestNative;
}
110 changes: 109 additions & 1 deletion src/test/java/org/prebid/server/bidder/grid/GridBidderTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.IntNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
import com.iab.openrtb.request.Banner;
import com.iab.openrtb.request.BidRequest;
import com.iab.openrtb.request.Imp;
import com.iab.openrtb.request.Native;
import com.iab.openrtb.request.Site;
import com.iab.openrtb.request.User;
import com.iab.openrtb.request.Video;
Expand All @@ -17,6 +19,7 @@
import org.prebid.server.bidder.grid.model.request.ExtImp;
import org.prebid.server.bidder.grid.model.request.ExtImpGridData;
import org.prebid.server.bidder.grid.model.request.ExtImpGridDataAdServer;
import org.prebid.server.bidder.grid.model.request.GridNative;
import org.prebid.server.bidder.grid.model.request.Keywords;
import org.prebid.server.bidder.grid.model.response.GridBidResponse;
import org.prebid.server.bidder.grid.model.response.GridSeatBid;
Expand All @@ -43,6 +46,7 @@
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.prebid.server.proto.openrtb.ext.response.BidType.banner;
import static org.prebid.server.proto.openrtb.ext.response.BidType.video;
import static org.prebid.server.proto.openrtb.ext.response.BidType.xNative;

public class GridBidderTest extends VertxTest {

Expand Down Expand Up @@ -115,6 +119,36 @@ public void makeHttpRequestsShouldCorrectlyModifyImpExt() {
.containsExactly(mapper.valueToTree(expectedExtImp));
}

@Test
public void makeHttpRequestsShouldCorrectlyModifyNative() {
// given
final String nativeRequest = "{\"eventtrackers\":[],\"context\":1,\"plcmttype\":2}";
final Imp nativeImp = Imp.builder()
.xNative(Native.builder().request(nativeRequest).ver("1.2").build())
.ext(mapper.valueToTree(ExtImp.builder().bidder(ExtImpGrid.of(1, null)).build()))
.build();
final BidRequest bidRequest = BidRequest.builder().imp(singletonList(nativeImp)).build();

// when
final Result<List<HttpRequest<BidRequest>>> result = target.makeHttpRequests(bidRequest);

// then
assertThat(result.getErrors()).isEmpty();
final ObjectNode expectedNativeRequest = mapper.createObjectNode();
expectedNativeRequest.set("eventtrackers", mapper.createArrayNode());
expectedNativeRequest.set("context", new IntNode(1));
expectedNativeRequest.set("plcmttype", new IntNode(2));
assertThat(result.getValue())
.extracting(HttpRequest::getPayload)
.flatExtracting(BidRequest::getImp)
.extracting(Imp::getXNative)
.containsExactly(GridNative.builder()
.requestNative(expectedNativeRequest)
.request(null)
.ver("1.2")
.build());
}

@Test
public void makeHttpRequestsShouldCorrectlyModifyRequestExt() throws IOException {
// given
Expand Down Expand Up @@ -289,7 +323,28 @@ public void makeBidsShouldReturnVideoBidIfNoBannerAndVideoIsPresent() throws Jso
}

@Test
public void makeBidsShouldReturnErrorIfImpHadNoBannerOrVideo() throws JsonProcessingException {
public void makeBidsShouldReturnNativeBidIfNoBannerAndNoVideoAndNativeIsPresent() throws JsonProcessingException {
// given
final BidderCall<BidRequest> httpCall = givenHttpCall(
BidRequest.builder()
.imp(singletonList(Imp.builder()
.xNative(Native.builder().build())
.id("123").build()))
.build(),
mapper.writeValueAsString(
givenBidResponse(givenBidNode())));

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

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

@Test
public void makeBidsShouldReturnErrorIfImpHadNoBannerOrVideoOrNative() throws JsonProcessingException {
// given
final BidderCall<BidRequest> httpCall = givenHttpCall(
BidRequest.builder()
Expand Down Expand Up @@ -381,6 +436,59 @@ public void makeBidsShouldModifyBidExtWithMetaIfDemandSourceIsPresentInBidExt()
.containsExactly(expectedBidExt);
}

@Test
public void makeBidsShouldNotReplacePresentAdmWithAdmNative() throws JsonProcessingException {
// given
final ObjectNode admNative = mapper.createObjectNode();
admNative.put("admNativeProperty", "admNativeValue");

final ObjectNode bidNode = givenBidNode()
.put("adm", "someAdm")
.set("adm_native", admNative);

final BidderCall<BidRequest> httpCall = givenHttpCall(
BidRequest.builder()
.imp(singletonList(Imp.builder().id("123").video(Video.builder().build()).build()))
.build(),
mapper.writeValueAsString(givenBidResponse(bidNode)));

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

// then
assertThat(result.getErrors()).isEmpty();
assertThat(result.getValue())
.extracting(BidderBid::getBid)
.extracting(Bid::getAdm)
.containsExactly("someAdm");
}

@Test
public void makeBidsShouldReplaceNotPresentAdmWithAdmNative() throws JsonProcessingException {
// given
final ObjectNode admNative = mapper.createObjectNode();
admNative.put("admNativeProperty", "admNativeValue");

final ObjectNode bidNode = givenBidNode()
.set("adm_native", admNative);

final BidderCall<BidRequest> httpCall = givenHttpCall(
BidRequest.builder()
.imp(singletonList(Imp.builder().id("123").video(Video.builder().build()).build()))
.build(),
mapper.writeValueAsString(givenBidResponse(bidNode)));

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

// then
assertThat(result.getErrors()).isEmpty();
assertThat(result.getValue())
.extracting(BidderBid::getBid)
.extracting(Bid::getAdm)
.containsExactly("{\"admNativeProperty\":\"admNativeValue\"}");
}

private static GridBidResponse givenBidResponse(
UnaryOperator<GridBidResponse.GridBidResponseBuilder> gridBidResponse,
ObjectNode objectNode) {
Expand Down
Loading