Skip to content

Commit

Permalink
Embimedia: New Adapter (#3140)
Browse files Browse the repository at this point in the history
  • Loading branch information
przemkaczmarek committed May 9, 2024
1 parent dc42b0b commit 6afc35c
Show file tree
Hide file tree
Showing 12 changed files with 287 additions and 0 deletions.
129 changes: 129 additions & 0 deletions src/main/java/org/prebid/server/bidder/loyal/LoyalBidder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package org.prebid.server.bidder.loyal;

import com.fasterxml.jackson.core.type.TypeReference;
import com.iab.openrtb.request.BidRequest;
import com.iab.openrtb.request.Imp;
import com.iab.openrtb.response.Bid;
import com.iab.openrtb.response.BidResponse;
import io.vertx.core.http.HttpMethod;
import org.apache.commons.collections4.CollectionUtils;
import org.prebid.server.bidder.Bidder;
import org.prebid.server.bidder.model.BidderBid;
import org.prebid.server.bidder.model.BidderCall;
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;
import org.prebid.server.proto.openrtb.ext.request.loyal.ExtImpLoyal;
import org.prebid.server.proto.openrtb.ext.response.BidType;
import org.prebid.server.util.HttpUtil;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

public class LoyalBidder implements Bidder<BidRequest> {

private static final TypeReference<ExtPrebid<?, ExtImpLoyal>> LOYAL_EXT_TYPE_REFERENCE =
new TypeReference<>() {
};

private static final String PLACEMENT_ID_MACRO = "{{PlacementId}}";
private static final String ENDPOINT_ID_MACRO = "{{EndpointId}}";

private final String endpointUrl;
private final JacksonMapper mapper;

public LoyalBidder(String endpointUrl, JacksonMapper mapper) {
this.endpointUrl = Objects.requireNonNull(endpointUrl);
this.mapper = Objects.requireNonNull(mapper);
}

@Override
public Result<List<HttpRequest<BidRequest>>> makeHttpRequests(BidRequest request) {
List<BidderError> errors = new ArrayList<>();
List<HttpRequest<BidRequest>> requests = new ArrayList<>();

for (Imp imp : request.getImp()) {
try {
ExtImpLoyal ext = parseImpExt(imp);
HttpRequest<BidRequest> httpRequest = createHttpRequest(ext, request);
requests.add(httpRequest);
} catch (Exception e) {
errors.add(BidderError.badInput("Failed to parse the ExtImpLoyal object: " + e.getMessage()));
}
}

if (!errors.isEmpty()) {
return Result.withErrors(errors);
}

return Result.withValues(requests);
}

private ExtImpLoyal parseImpExt(Imp imp) {
final ExtImpLoyal extImpLoyal;
try {
extImpLoyal = mapper.mapper().convertValue(imp.getExt(), LOYAL_EXT_TYPE_REFERENCE).getBidder();
} catch (IllegalArgumentException e) {
throw new PreBidException("Missing bidder ext in impression with id: " + imp.getId());
}
return extImpLoyal;
}

private HttpRequest<BidRequest> createHttpRequest(ExtImpLoyal ext, BidRequest request) {
String url = endpointUrl
.replace(PLACEMENT_ID_MACRO, ext.getPlacementId())
.replace(ENDPOINT_ID_MACRO, ext.getEndpointId());
BidRequest outgoingRequest = request.toBuilder().build();
return HttpRequest.<BidRequest>builder()
.method(HttpMethod.POST)
.uri(url)
.body(mapper.encodeToBytes(outgoingRequest))
.headers(HttpUtil.headers())
.payload(outgoingRequest)
.build();
}

@Override
public Result<List<BidderBid>> makeBids(BidderCall<BidRequest> httpCall, BidRequest bidRequest) {
try {
BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class);
return Result.withValues(extractBids(bidResponse));
} catch (DecodeException | PreBidException e) {
return Result.withError(BidderError.badServerResponse(e.getMessage()));
}
}

private static List<BidderBid> extractBids(BidResponse bidResponse) {
if (bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid())) {
throw new PreBidException("Empty SeatBid array");
}
return bidResponse.getSeatbid()
.stream()
.flatMap(seatBid -> Optional.ofNullable(seatBid.getBid()).orElse(List.of()).stream())
.map(bid -> BidderBid.of(bid, getBidMediaType(bid), bidResponse.getCur()))
.toList();
}

private static BidType getBidMediaType(Bid bid) {
final Integer markupType = bid.getMtype();
if (markupType == null) {
throw new PreBidException("Missing MType for bid: " + bid.getId());
}

return switch (markupType) {
case 1 -> BidType.banner;
case 2 -> BidType.video;
case 3 -> BidType.audio;
case 4 -> BidType.xNative;
default -> throw new PreBidException(
"Unable to fetch mediaType " + bid.getMtype() + " in multi-format: " + bid.getImpid());
};
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.prebid.server.proto.openrtb.ext.request.loyal;

import lombok.Value;

@Value(staticConstructor = "of")
public class ExtImpLoyal {

String placementId;

String endpointId;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package org.prebid.server.spring.config.bidder;

import jakarta.validation.constraints.NotBlank;

import org.prebid.server.bidder.BidderDeps;
import org.prebid.server.bidder.loyal.LoyalBidder;
import org.prebid.server.json.JacksonMapper;
import org.prebid.server.spring.config.bidder.model.BidderConfigurationProperties;
import org.prebid.server.spring.config.bidder.util.BidderDepsAssembler;
import org.prebid.server.spring.config.bidder.util.UsersyncerCreator;
import org.prebid.server.spring.env.YamlPropertySourceFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

@Configuration
@PropertySource(value = "classpath:/bidder-config/loyal.yaml", factory = YamlPropertySourceFactory.class)
public class LoyalConfiguration {

private static final String BIDDER_NAME = "loyal";

@Bean("loyalConfigurationProperties")
@ConfigurationProperties("adapters.loyal")
BidderConfigurationProperties configurationProperties() {
return new BidderConfigurationProperties();
}

@Bean
BidderDeps loaylBidderDeps(BidderConfigurationProperties loyalConfigurationProperties,
@NotBlank @Value("${external-url}") String externalUrl,
JacksonMapper mapper) {

return BidderDepsAssembler.forBidder(BIDDER_NAME)
.withConfig(loyalConfigurationProperties)
.usersyncerCreator(UsersyncerCreator.create(externalUrl))
.bidderCreator(config -> new LoyalBidder(config.getEndpoint(), mapper))
.assemble();
}
}
27 changes: 27 additions & 0 deletions src/main/resources/bidder-config/loyal.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
adapters:
loyal:
enabled: true
endpoint: "https://us-east-1.loyal.app/pserver"
pbs-enforces-gdpr: false
pbs-enforces-ccpa: false
modifying-vast-xml-allowed: false
geo-target:
- USA
meta-info:
maintainer-email: "[email protected]"
app-media-types:
- banner
- video
- native
site-media-types:
- banner
- video
- native
supported-vendors: []
vendor-id: 0
usersync:
url: ""
redirect-url: ""
cookie-family-name: loyal
type: redirect
support-cors: false
23 changes: 23 additions & 0 deletions src/main/resources/static/bidder-params/loyal.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Loyal Adapter Params",
"description": "A schema which validates params accepted by the Loyal adapter",
"type": "object",

"properties": {
"placementId": {
"type": "string",
"minLength": 1,
"description": "Placement ID"
},
"endpointId": {
"type": "string",
"minLength": 1,
"description": "Endpoint ID"
}
},
"oneOf": [
{ "required": ["placementId"] },
{ "required": ["endpointId"] }
]
}
19 changes: 19 additions & 0 deletions src/test/java/org/prebid/server/bidder/loyal/LoyalBidderTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package org.prebid.server.bidder.loyal;

import org.junit.Test;
import org.prebid.server.VertxTest;

import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;


public class LoyalBidderTest extends VertxTest {

private static final String ENDPOINT_URL = "https://test.com/test?param={{PlacementId}}";

private final LoyalBidder target = new LoyalBidder(ENDPOINT_URL, jacksonMapper);

@Test
public void creationShouldFailOnInvalidEndpointUrl() {
assertThatIllegalArgumentException().isThrownBy(() -> new LoyalBidder("invalid_url", jacksonMapper));
}
}
35 changes: 35 additions & 0 deletions src/test/java/org/prebid/server/it/LoyalTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package org.prebid.server.it;

import io.restassured.response.Response;
import org.json.JSONException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.prebid.server.model.Endpoint;
import org.springframework.test.context.junit4.SpringRunner;

import java.io.IOException;

import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson;
import static com.github.tomakehurst.wiremock.client.WireMock.post;
import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo;
import static java.util.Collections.singletonList;

@RunWith(SpringRunner.class)
public class LoyalTest extends IntegrationTest {

@Test
public void openrtb2AuctionShouldRespondWithBidsFromLoyal() throws IOException, JSONException {
// given
WIRE_MOCK_RULE.stubFor(post(urlPathEqualTo("/loyal-exchange"))
.withRequestBody(equalToJson(jsonFrom("openrtb2/loyal/test-loyal-bid-request.json")))
.willReturn(aResponse().withBody(jsonFrom("openrtb2/loyal/test-loyal-bid-response.json"))));

// when
final Response response = responseFor("openrtb2/loyal/test-auction-loyal-request.json",
Endpoint.openrtb2_auction);

// then
assertJsonEquals("openrtb2/loyal/test-auction-loyal-response.json", response, singletonList("loyal"));
}
}
Empty file.
Empty file.
Empty file.
Empty file.
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,8 @@ adapters.yearxero.enabled=true
adapters.yearxero.endpoint=http://localhost:8090/yearxero-exchange
adapters.minutemedia.enabled=true
adapters.minutemedia.endpoint=http://localhost:8090/minutemedia-exchange?publisherId={{PublisherId}}
adapters.loyal.enabled=true
adapters.loyal.endpoint=http://localhost:8090/loyal-exchange
http-client.circuit-breaker.enabled=true
http-client.circuit-breaker.idle-expire-hours=24
http-client.circuit-breaker.opening-threshold=1000
Expand Down

0 comments on commit 6afc35c

Please sign in to comment.