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

Liftoff Site Support #2855

Merged
merged 4 commits into from
Jan 5, 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
85 changes: 38 additions & 47 deletions src/main/java/org/prebid/server/bidder/liftoff/LiftoffBidder.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.iab.openrtb.request.App;
import com.iab.openrtb.request.BidRequest;
import com.iab.openrtb.request.Imp;
import com.iab.openrtb.request.Site;
import com.iab.openrtb.request.User;
import com.iab.openrtb.response.BidResponse;
import com.iab.openrtb.response.SeatBid;
Expand Down Expand Up @@ -38,13 +39,13 @@

public class LiftoffBidder implements Bidder<BidRequest> {

private static final String BIDDER_CURRENCY = "USD";
private static final String X_OPENRTB_VERSION = "2.5";

private final String endpointUrl;
private final CurrencyConversionService currencyConversionService;
private final JacksonMapper mapper;

private static final String BIDDER_CURRENCY = "USD";
private static final String X_OPENRTB_VERSION = "2.5";

public LiftoffBidder(String endpointUrl,
CurrencyConversionService currencyConversionService,
JacksonMapper mapper) {
Expand All @@ -60,39 +61,25 @@ public Result<List<HttpRequest<BidRequest>>> makeHttpRequests(BidRequest bidRequ
final List<HttpRequest<BidRequest>> httpRequests = new ArrayList<>();

for (Imp imp : bidRequest.getImp()) {
final Imp.ImpBuilder impBuilder = imp.toBuilder();
final Price price;
final ObjectNode convertedExtImpLiftoff;
final ExtImpLiftoff liftoffImpressionExtBidder;
try {
price = resolveBidFloor(imp, bidRequest);
final LiftoffImpressionExt liftoffImpressionExt = resolveLiftoffImpressionExt(imp);
liftoffImpressionExtBidder = liftoffImpressionExt.getBidder();
convertedExtImpLiftoff = resolveExtImpLiftoff(
resolveLiftoffImpExt(bidRequest, liftoffImpressionExtBidder, liftoffImpressionExt));
final Price price = resolveBidFloor(imp, bidRequest);
final LiftoffImpressionExt impExt = parseImpExt(imp);
final LiftoffImpressionExt modifiedImpExt = modifyImpExt(impExt, bidRequest);
final Imp modifiedImp = modifyImp(imp, modifiedImpExt, price);
final BidRequest modifiedRequest = modifyBidRequest(
bidRequest,
modifiedImp,
modifiedImpExt.getBidder().getAppStoreId());

httpRequests.add(makeHttpRequest(modifiedRequest));
} catch (PreBidException e) {
errors.add(BidderError.badInput(e.getMessage()));
continue;
}

httpRequests.add(createRequest(
prepareBidRequest(bidRequest,
prepareImp(impBuilder, liftoffImpressionExtBidder, convertedExtImpLiftoff, price, imp),
liftoffImpressionExtBidder)));
}

return Result.of(httpRequests, errors);
}

private static LiftoffImpressionExt resolveLiftoffImpExt(BidRequest bidRequest,
ExtImpLiftoff liftoffImpressionExtBidder,
LiftoffImpressionExt liftoffImpressionExt) {
return liftoffImpressionExt.toBuilder()
.vungle(createExtImpLiftoff(
ObjectUtil.getIfNotNull(bidRequest.getUser(), User::getBuyeruid), liftoffImpressionExtBidder))
.build();
}

private Price resolveBidFloor(Imp imp, BidRequest bidRequest) {
BigDecimal bigDecimal = null;
if (BidderUtil.isValidPrice(imp.getBidfloor())
Expand All @@ -105,40 +92,44 @@ private Price resolveBidFloor(Imp imp, BidRequest bidRequest) {
return Price.of(BIDDER_CURRENCY, bigDecimal);
}

private ObjectNode resolveExtImpLiftoff(LiftoffImpressionExt impressionExt) {
return mapper.mapper().convertValue(impressionExt, ObjectNode.class);
private LiftoffImpressionExt parseImpExt(Imp imp) {
return mapper.mapper().convertValue(imp.getExt(), LiftoffImpressionExt.class);
}

private LiftoffImpressionExt resolveLiftoffImpressionExt(Imp imp) {
return mapper.mapper().convertValue(imp.getExt(), LiftoffImpressionExt.class);
private static LiftoffImpressionExt modifyImpExt(LiftoffImpressionExt impExt, BidRequest bidRequest) {
final ExtImpLiftoff bidder = impExt.getBidder();
final String buyerId = ObjectUtil.getIfNotNull(bidRequest.getUser(), User::getBuyeruid);
final ExtImpLiftoff vungle = ExtImpLiftoff.of(
buyerId,
bidder.getAppStoreId(),
bidder.getPlacementReferenceId());

return impExt.toBuilder().vungle(vungle).build();
}

private static Imp prepareImp(Imp.ImpBuilder impBuilder,
ExtImpLiftoff extImpLiftoff,
ObjectNode convertedExtImpLiftoff,
Price price,
Imp imp) {
return impBuilder
.tagid(extImpLiftoff.getPlacementReferenceId())
.ext(convertedExtImpLiftoff)
private Imp modifyImp(Imp imp, LiftoffImpressionExt modifiedImpExt, Price price) {
return imp.toBuilder()
.tagid(modifiedImpExt.getBidder().getPlacementReferenceId())
.ext(mapper.mapper().convertValue(modifiedImpExt, ObjectNode.class))
.bidfloor(price.getValue() != null ? price.getValue() : imp.getBidfloor())
.bidfloorcur(price.getValue() != null ? price.getCurrency() : imp.getBidfloorcur())
.build();
}

private static ExtImpLiftoff createExtImpLiftoff(String buyeruid, ExtImpLiftoff extImpLiftoff) {
return ExtImpLiftoff.of(buyeruid, extImpLiftoff.getAppStoreId(), extImpLiftoff.getPlacementReferenceId());
}

private static BidRequest prepareBidRequest(BidRequest bidRequest, Imp imp, ExtImpLiftoff extImpLiftoff) {
final App app = ObjectUtil.getIfNotNull(bidRequest, BidRequest::getApp);
private static BidRequest modifyBidRequest(BidRequest bidRequest, Imp imp, String appStoreId) {
final App app = bidRequest.getApp();
final Site site = bidRequest.getSite();
if (app == null && site == null) {
throw new PreBidException("The bid request must have an app or site object");
}
return bidRequest.toBuilder()
.imp(Collections.singletonList(imp))
.app(app != null ? app.toBuilder().id(extImpLiftoff.getAppStoreId()).build() : null)
.app(app == null ? App.builder().id(appStoreId).build() : app.toBuilder().id(appStoreId).build())
.site(null)
.build();
}

private HttpRequest<BidRequest> createRequest(BidRequest request) {
private HttpRequest<BidRequest> makeHttpRequest(BidRequest request) {
return HttpRequest.<BidRequest>builder()
.method(HttpMethod.POST)
.uri(endpointUrl)
Expand Down
2 changes: 2 additions & 0 deletions src/main/resources/bidder-config/liftoff.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,7 @@ adapters:
maintainer-email: vxssp@liftoff.io
app-media-types:
- video
site-media-types:
- video
supported-vendors:
vendor-id: 0
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.iab.openrtb.request.Banner;
import com.iab.openrtb.request.BidRequest;
import com.iab.openrtb.request.Imp;
import com.iab.openrtb.request.Site;
import com.iab.openrtb.request.User;
import com.iab.openrtb.request.Video;
import com.iab.openrtb.response.Bid;
Expand Down Expand Up @@ -39,6 +40,7 @@
import static java.util.function.UnaryOperator.identity;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.assertj.core.groups.Tuple.tuple;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.BDDMockito.given;
Expand Down Expand Up @@ -205,8 +207,8 @@ public void makeHttpRequestShouldUpdateExtImpLiftoffWhenUserBuyeruidPresent() {
@Test
public void makeHttpRequestShouldUpdateAppIdWhenExtImpLiftoffContainAppStoreId() {
// given
final BidRequest bidRequest = givenBidRequest(bidRequestBuilder -> bidRequestBuilder.app(App.builder().build()),
identity());
final App givenApp = App.builder().name("appName").build();
final BidRequest bidRequest = givenBidRequest(bidRequestBuilder -> bidRequestBuilder.app(givenApp), identity());

// when
final Result<List<HttpRequest<BidRequest>>> result = target.makeHttpRequests(bidRequest);
Expand All @@ -216,8 +218,45 @@ public void makeHttpRequestShouldUpdateAppIdWhenExtImpLiftoffContainAppStoreId()
assertThat(result.getValue())
.extracting(HttpRequest::getPayload)
.extracting(BidRequest::getApp)
.extracting(App::getId)
.containsExactly("any-app-store-id");
.containsExactly(givenApp.toBuilder().id("any-app-store-id").build());
}

@Test
public void makeHttpRequestShouldCreateAppWithIdWhenExtImpLiftoffContainAppStoreIdAndAppIsAbsentAndSiteIsPresent() {
// given
final BidRequest bidRequest = givenBidRequest(bidRequestBuilder -> bidRequestBuilder
.site(Site.builder().build())
.app(null),
identity());

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

// then
assertThat(result.getErrors()).isEmpty();
assertThat(result.getValue())
.extracting(HttpRequest::getPayload)
.extracting(BidRequest::getApp, BidRequest::getSite)
.containsExactly(tuple(App.builder().id("any-app-store-id").build(), null));
}

@Test
public void makeHttpRequestShouldReturnErrorWhenAppAndSiteAreAbsent() {
// given
final BidRequest bidRequest = givenBidRequest(
bidRequestBuilder -> bidRequestBuilder.site(null).app(null),
identity());

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

// then
assertThat(result.getErrors()).hasSize(1)
.allSatisfy(error -> {
assertThat(error.getType()).isEqualTo(BidderError.Type.bad_input);
assertThat(error.getMessage()).startsWith("The bid request must have an app or site object");
});
assertThat(result.getValue()).isEmpty();
}

@Test
Expand Down Expand Up @@ -348,6 +387,7 @@ private static BidRequest givenBidRequest(
UnaryOperator<Imp.ImpBuilder>... impCustomizer) {

return bidRequestCustomizer.apply(BidRequest.builder()
.app(App.builder().build())
.imp(Arrays.stream(impCustomizer).map(LiftoffBidderTest::givenImp).toList()))
.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
},
"ext": {
"liftoff": {
"bid_token": "123",
"app_store_id": "123",
"placement_reference_id": "123"
"bid_token": "bid_token",
"app_store_id": "app_store_id",
"placement_reference_id": "placement_reference_id"
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"imp": [
{
"id": "imp_id",
"tagid" : "123",
"tagid" : "placement_reference_id",
"secure": 1,
"video": {
"mimes": [
Expand All @@ -14,29 +14,22 @@
},
"ext": {
"bidder" : {
"bid_token" : "123",
"app_store_id" : "123",
"placement_reference_id" : "123"
"bid_token" : "bid_token",
"app_store_id" : "app_store_id",
"placement_reference_id" : "placement_reference_id"
},
"vungle" : {
"app_store_id" : "123",
"placement_reference_id" : "123"
"app_store_id" : "app_store_id",
"placement_reference_id" : "placement_reference_id"
}
}
}
],
"source": {
"tid": "${json-unit.any-string}"
},
"site": {
"domain": "www.example.com",
"page": "http://www.example.com",
"publisher": {
"domain": "example.com"
},
"ext": {
"amp": 0
}
"app": {
"id": "app_store_id"
},
"device": {
"ua": "userAgent",
Expand Down
Loading