Skip to content

Commit d64feeb

Browse files
ResetDigital: Switch to OpenRTB
1 parent 0e8ca15 commit d64feeb

8 files changed

Lines changed: 280 additions & 416 deletions

File tree

Lines changed: 101 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.prebid.server.bidder.resetdigital;
22

3+
import com.fasterxml.jackson.core.type.TypeReference;
34
import com.iab.openrtb.request.BidRequest;
45
import com.iab.openrtb.request.Imp;
56
import com.iab.openrtb.response.Bid;
@@ -12,171 +13,165 @@
1213
import org.prebid.server.bidder.model.BidderCall;
1314
import org.prebid.server.bidder.model.BidderError;
1415
import org.prebid.server.bidder.model.HttpRequest;
15-
import org.prebid.server.bidder.model.Price;
1616
import org.prebid.server.bidder.model.Result;
17-
import org.prebid.server.currency.CurrencyConversionService;
1817
import org.prebid.server.exception.PreBidException;
1918
import org.prebid.server.json.DecodeException;
2019
import org.prebid.server.json.JacksonMapper;
20+
import org.prebid.server.proto.openrtb.ext.ExtPrebid;
21+
import org.prebid.server.proto.openrtb.ext.request.resetdigital.ExtImpResetDigital;
2122
import org.prebid.server.proto.openrtb.ext.response.BidType;
2223
import org.prebid.server.util.BidderUtil;
2324
import org.prebid.server.util.HttpUtil;
2425

25-
import java.math.BigDecimal;
2626
import java.util.ArrayList;
27-
import java.util.Collection;
2827
import java.util.Collections;
2928
import java.util.List;
3029
import java.util.Objects;
31-
import java.util.stream.Stream;
3230

3331
public class ResetDigitalBidder implements Bidder<BidRequest> {
3432

3533
private static final String DEFAULT_CURRENCY = "USD";
34+
private static final TypeReference<ExtPrebid<?, ExtImpResetDigital>> EXT_TYPE_REFERENCE =
35+
new TypeReference<>() {
36+
};
3637

3738
private final String endpointUrl;
38-
private final CurrencyConversionService currencyConversionService;
3939
private final JacksonMapper mapper;
4040

41-
public ResetDigitalBidder(String endpointUrl,
42-
CurrencyConversionService currencyConversionService,
43-
JacksonMapper mapper) {
44-
41+
public ResetDigitalBidder(String endpointUrl, JacksonMapper mapper) {
4542
this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl));
46-
this.currencyConversionService = Objects.requireNonNull(currencyConversionService);
4743
this.mapper = Objects.requireNonNull(mapper);
4844
}
4945

5046
@Override
5147
public Result<List<HttpRequest<BidRequest>>> makeHttpRequests(BidRequest request) {
52-
final List<Imp> bannerImps = new ArrayList<>();
53-
final List<Imp> videoImps = new ArrayList<>();
54-
final List<Imp> audioImps = new ArrayList<>();
55-
Price bidFloorPrice;
56-
57-
for (Imp imp : request.getImp()) {
58-
try {
59-
bidFloorPrice = resolveBidFloor(imp, request);
60-
} catch (PreBidException e) {
61-
return Result.withError(BidderError.badInput(e.getMessage()));
62-
}
63-
populateBannerImps(bannerImps, bidFloorPrice, imp);
64-
populateVideoImps(videoImps, bidFloorPrice, imp);
65-
populateAudiImps(audioImps, bidFloorPrice, imp);
48+
if (CollectionUtils.isEmpty(request.getImp()) || request.getImp().size() != 1) {
49+
return Result.withError(BidderError.badInput(
50+
"ResetDigital adapter supports only one impression per request"));
6651
}
6752

68-
return Result.withValues(getHttpRequests(request, bannerImps, videoImps, audioImps));
69-
}
70-
71-
private List<HttpRequest<BidRequest>> getHttpRequests(BidRequest request,
72-
List<Imp> bannerImps,
73-
List<Imp> videoImps,
74-
List<Imp> audioImps) {
75-
76-
return Stream.of(bannerImps, videoImps, audioImps)
77-
.filter(CollectionUtils::isNotEmpty)
78-
.map(imp -> makeHttpRequest(request, imp))
79-
.toList();
80-
}
81-
82-
private HttpRequest<BidRequest> makeHttpRequest(BidRequest bidRequest, List<Imp> imp) {
83-
final BidRequest outgoingRequest = bidRequest.toBuilder().imp(imp).build();
84-
85-
return BidderUtil.defaultRequest(outgoingRequest, endpointUrl, mapper);
86-
}
53+
final Imp imp = request.getImp().getFirst();
54+
final ExtImpResetDigital extImp;
55+
try {
56+
extImp = parseImpExt(imp);
57+
} catch (PreBidException e) {
58+
return Result.withError(BidderError.badInput(e.getMessage()));
59+
}
8760

88-
private static Imp modifyImp(Imp imp, Price bidFloorPrice) {
89-
return imp.toBuilder()
90-
.bidfloorcur(bidFloorPrice.getCurrency())
91-
.bidfloor(bidFloorPrice.getValue())
61+
final Imp modifiedImp = modifyImp(imp, extImp);
62+
final BidRequest outgoingRequest = request.toBuilder()
63+
.imp(Collections.singletonList(modifiedImp))
9264
.build();
93-
}
9465

95-
private Price resolveBidFloor(Imp imp, BidRequest bidRequest) {
96-
final Price initialBidFloorPrice = Price.of(imp.getBidfloorcur(), imp.getBidfloor());
97-
return BidderUtil.isValidPrice(initialBidFloorPrice)
98-
? convertBidFloor(initialBidFloorPrice, imp.getId(), bidRequest)
99-
: initialBidFloorPrice;
66+
final String uri = endpointUrl + "?pid=" + HttpUtil.encodeUrl(extImp.getPlacementId());
67+
68+
return Result.withValue(BidderUtil.defaultRequest(outgoingRequest, uri, mapper));
10069
}
10170

102-
private Price convertBidFloor(Price bidFloorPrice, String impId, BidRequest bidRequest) {
103-
final String bidFloorCur = bidFloorPrice.getCurrency();
71+
private ExtImpResetDigital parseImpExt(Imp imp) {
10472
try {
105-
final BigDecimal convertedPrice = currencyConversionService
106-
.convertCurrency(bidFloorPrice.getValue(), bidRequest, bidFloorCur, DEFAULT_CURRENCY);
73+
final ExtPrebid<?, ExtImpResetDigital> extPrebid = mapper.mapper()
74+
.convertValue(imp.getExt(), EXT_TYPE_REFERENCE);
10775

108-
return Price.of(DEFAULT_CURRENCY, convertedPrice);
109-
} catch (PreBidException e) {
110-
throw new PreBidException(
111-
"Unable to convert provided bid floor currency from %s to %s for imp `%s`"
112-
.formatted(bidFloorCur, DEFAULT_CURRENCY, impId));
113-
}
114-
}
76+
if (extPrebid == null || extPrebid.getBidder() == null) {
77+
throw new PreBidException("imp.ext.bidder is required");
78+
}
11579

116-
private static void populateBannerImps(List<Imp> bannerImps, Price bidFloorPrice, Imp imp) {
117-
if (imp.getBanner() != null) {
118-
final Imp bannerImp = imp.toBuilder().video(null).xNative(null).audio(null).build();
119-
bannerImps.add(modifyImp(bannerImp, bidFloorPrice));
80+
return extPrebid.getBidder();
81+
} catch (IllegalArgumentException e) {
82+
throw new PreBidException("Error parsing resetDigitalExt from imp.ext: " + e.getMessage());
12083
}
12184
}
12285

123-
private static void populateVideoImps(List<Imp> videoImps, Price bidFloorPrice, Imp imp) {
124-
if (imp.getVideo() != null) {
125-
final Imp videoImp = imp.toBuilder().banner(null).xNative(null).audio(null).build();
126-
videoImps.add(modifyImp(videoImp, bidFloorPrice));
127-
}
128-
}
86+
private static Imp modifyImp(Imp imp, ExtImpResetDigital extImp) {
87+
final Imp.ImpBuilder impBuilder = imp.toBuilder();
12988

130-
private static void populateAudiImps(List<Imp> audioImps, Price bidFloorPrice, Imp imp) {
131-
if (imp.getAudio() != null) {
132-
final Imp audioImp = imp.toBuilder().banner(null).xNative(null).video(null).build();
133-
audioImps.add(modifyImp(audioImp, bidFloorPrice));
89+
if (StringUtils.isBlank(imp.getTagid())) {
90+
impBuilder.tagid(extImp.getPlacementId());
13491
}
92+
93+
return impBuilder.build();
13594
}
13695

13796
@Override
13897
public final Result<List<BidderBid>> makeBids(BidderCall<BidRequest> httpCall, BidRequest bidRequest) {
13998
try {
14099
final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class);
141-
return Result.withValues(extractBids(bidResponse, httpCall.getRequest().getPayload()));
100+
return extractBids(bidResponse, httpCall.getRequest().getPayload());
142101
} catch (DecodeException | PreBidException e) {
143102
return Result.withError(BidderError.badServerResponse(e.getMessage()));
144103
}
145104
}
146105

147-
private static List<BidderBid> extractBids(BidResponse bidResponse, BidRequest bidRequest) {
106+
private static Result<List<BidderBid>> extractBids(BidResponse bidResponse, BidRequest bidRequest) {
148107
if (bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid())) {
149-
return Collections.emptyList();
150-
}
151-
if (bidResponse.getCur() != null && !StringUtils.equalsIgnoreCase(DEFAULT_CURRENCY, bidResponse.getCur())) {
152-
throw new PreBidException("Bidder support only USD currency");
108+
return Result.withValues(Collections.emptyList());
153109
}
154-
return bidsFromResponse(bidResponse, bidRequest);
155-
}
156110

157-
private static List<BidderBid> bidsFromResponse(BidResponse bidResponse, BidRequest bidRequest) {
158-
return bidResponse.getSeatbid().stream()
159-
.filter(Objects::nonNull)
160-
.map(SeatBid::getBid)
161-
.filter(Objects::nonNull)
162-
.flatMap(Collection::stream)
163-
.map(bid -> BidderBid.of(bid, getBidType(bid, bidRequest.getImp()), DEFAULT_CURRENCY))
164-
.toList();
111+
final String currency = StringUtils.isNotBlank(bidResponse.getCur())
112+
? bidResponse.getCur()
113+
: DEFAULT_CURRENCY;
114+
115+
return bidsFromResponse(bidResponse, bidRequest, currency);
165116
}
166117

167-
private static BidType getBidType(Bid bid, List<Imp> imps) {
168-
final String impId = bid.getImpid();
169-
for (Imp imp : imps) {
170-
if (imp.getId().equals(impId)) {
171-
if (imp.getBanner() != null) {
172-
return BidType.banner;
173-
} else if (imp.getVideo() != null) {
174-
return BidType.video;
175-
} else if (imp.getAudio() != null) {
176-
return BidType.audio;
118+
private static Result<List<BidderBid>> bidsFromResponse(BidResponse bidResponse,
119+
BidRequest bidRequest,
120+
String currency) {
121+
final List<BidderBid> bids = new ArrayList<>();
122+
final List<BidderError> errors = new ArrayList<>();
123+
124+
for (SeatBid seatBid : bidResponse.getSeatbid()) {
125+
if (seatBid == null || seatBid.getBid() == null) {
126+
continue;
127+
}
128+
for (Bid bid : seatBid.getBid()) {
129+
if (!BidderUtil.isValidPrice(bid.getPrice())) {
130+
errors.add(BidderError.badServerResponse(
131+
"price %s <= 0 filtered out".formatted(bid.getPrice())));
132+
continue;
133+
}
134+
135+
try {
136+
final BidType bidType = getBidType(bid, bidRequest);
137+
bids.add(BidderBid.of(bid, bidType, currency));
138+
} catch (PreBidException e) {
139+
errors.add(BidderError.badServerResponse(e.getMessage()));
177140
}
178141
}
179142
}
180-
throw new PreBidException("Failed to find banner/video/audio impression " + impId);
143+
144+
return Result.of(bids, errors);
145+
}
146+
147+
private static BidType getBidType(Bid bid, BidRequest bidRequest) {
148+
final Integer mtype = bid.getMtype();
149+
if (mtype != null) {
150+
return switch (mtype) {
151+
case 1 -> BidType.banner;
152+
case 2 -> BidType.video;
153+
case 3 -> BidType.audio;
154+
case 4 -> BidType.xNative;
155+
default -> throw new PreBidException("Unsupported MType: " + mtype);
156+
};
157+
}
158+
159+
final Imp imp = bidRequest.getImp().getFirst();
160+
if (!imp.getId().equals(bid.getImpid())) {
161+
throw new PreBidException("No matching impression found for ImpID: " + bid.getImpid());
162+
}
163+
164+
return getMediaType(imp);
165+
}
166+
167+
private static BidType getMediaType(Imp imp) {
168+
if (imp.getVideo() != null) {
169+
return BidType.video;
170+
} else if (imp.getAudio() != null) {
171+
return BidType.audio;
172+
} else if (imp.getXNative() != null) {
173+
return BidType.xNative;
174+
}
175+
return BidType.banner;
181176
}
182177
}

src/main/java/org/prebid/server/spring/config/bidder/ResetDigitalConfiguration.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import org.prebid.server.bidder.BidderDeps;
44
import org.prebid.server.bidder.resetdigital.ResetDigitalBidder;
5-
import org.prebid.server.currency.CurrencyConversionService;
65
import org.prebid.server.json.JacksonMapper;
76
import org.prebid.server.spring.config.bidder.model.BidderConfigurationProperties;
87
import org.prebid.server.spring.config.bidder.util.BidderDepsAssembler;
@@ -31,16 +30,12 @@ BidderConfigurationProperties configurationProperties() {
3130
@Bean
3231
BidderDeps resetDigitalBidderDeps(BidderConfigurationProperties resetDigitalConfigurationProperties,
3332
@NotBlank @Value("${external-url}") String externalUrl,
34-
CurrencyConversionService currencyConversionService,
3533
JacksonMapper mapper) {
3634

3735
return BidderDepsAssembler.forBidder(BIDDER_NAME)
3836
.withConfig(resetDigitalConfigurationProperties)
3937
.usersyncerCreator(UsersyncerCreator.create(externalUrl))
40-
.bidderCreator(config -> new ResetDigitalBidder(
41-
config.getEndpoint(),
42-
currencyConversionService,
43-
mapper))
38+
.bidderCreator(config -> new ResetDigitalBidder(config.getEndpoint(), mapper))
4439
.assemble();
4540
}
4641
}
Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,24 @@
11
adapters:
22
resetdigital:
3-
endpoint: http://b-us-east14.resetdigital.co:9001
3+
endpoint: https://prebid.resetdigital.co
4+
endpoint-compression: gzip
45
meta-info:
56
maintainer-email: biddersupport@resetdigital.co
67
app-media-types:
78
- banner
89
- video
10+
- native
911
- audio
1012
site-media-types:
1113
- banner
1214
- video
15+
- native
1316
- audio
1417
supported-vendors:
1518
vendor-id: 1162
1619
usersync:
1720
cookie-family-name: resetdigital
1821
redirect:
19-
url: https://sync.resetdigital.co/csync?pid=rubicon&redir={{redirect_url}}
20-
support-cors: false
21-
uid-macro: '$USER_ID'
22+
url: https://sync.resetdigital.co/usersync?gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&us_privacy={{us_privacy}}&redirect={{redirect_url}}
23+
support-cors: true
24+
uid-macro: '$UID'

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

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,12 @@
44
"description": "A schema which validates params accepted by the ResetDigital adapter",
55
"type": "object",
66
"properties": {
7-
"pubId": {
7+
"placement_id": {
88
"type": "string",
9-
"description": "The publisher's ID provided"
10-
},
11-
"zoneId": {
12-
"type": "string",
13-
"description": "Zone ID"
14-
},
15-
"forceBid": {
16-
"type": "boolean",
17-
"description": "Force bids with a test creative"
9+
"description": "Placement ID provided by ResetDigital",
10+
"minLength": 1
1811
}
1912
},
20-
"required": [
21-
"pubId"
22-
]
13+
"required": ["placement_id"],
14+
"additionalProperties": false
2315
}

0 commit comments

Comments
 (0)