Skip to content

Commit c26640f

Browse files
New Adapter: Start.io (#3941)
1 parent 8a86567 commit c26640f

File tree

11 files changed

+1038
-0
lines changed

11 files changed

+1038
-0
lines changed
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
package org.prebid.server.bidder.startio;
2+
3+
import com.fasterxml.jackson.core.JsonPointer;
4+
import com.fasterxml.jackson.databind.JsonNode;
5+
import com.iab.openrtb.request.App;
6+
import com.iab.openrtb.request.BidRequest;
7+
import com.iab.openrtb.request.Imp;
8+
import com.iab.openrtb.request.Site;
9+
import com.iab.openrtb.response.Bid;
10+
import com.iab.openrtb.response.BidResponse;
11+
import com.iab.openrtb.response.SeatBid;
12+
import org.apache.commons.collections4.CollectionUtils;
13+
import org.apache.commons.lang3.StringUtils;
14+
import org.prebid.server.bidder.Bidder;
15+
import org.prebid.server.bidder.model.BidderBid;
16+
import org.prebid.server.bidder.model.BidderCall;
17+
import org.prebid.server.bidder.model.BidderError;
18+
import org.prebid.server.bidder.model.HttpRequest;
19+
import org.prebid.server.bidder.model.Result;
20+
import org.prebid.server.json.DecodeException;
21+
import org.prebid.server.json.JacksonMapper;
22+
import org.prebid.server.proto.openrtb.ext.response.BidType;
23+
import org.prebid.server.util.BidderUtil;
24+
import org.prebid.server.util.HttpUtil;
25+
26+
import java.util.ArrayList;
27+
import java.util.Collection;
28+
import java.util.Collections;
29+
import java.util.List;
30+
import java.util.Objects;
31+
32+
public class StartioBidder implements Bidder<BidRequest> {
33+
34+
private static final JsonPointer BID_TYPE_POINTER = JsonPointer.valueOf("/prebid/type");
35+
private static final String BID_CURRENCY = "USD";
36+
37+
private final String endpointUrl;
38+
private final JacksonMapper mapper;
39+
40+
public StartioBidder(String endpointUrl, JacksonMapper mapper) {
41+
this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl));
42+
this.mapper = Objects.requireNonNull(mapper);
43+
}
44+
45+
@Override
46+
public final Result<List<HttpRequest<BidRequest>>> makeHttpRequests(BidRequest bidRequest) {
47+
if (hasNoAppOrSiteId(bidRequest)) {
48+
return Result.withError(BidderError.badInput(
49+
"Bidder requires either app.id or site.id to be specified."));
50+
}
51+
52+
if (isSupportedCurrency(bidRequest.getCur())) {
53+
return Result.withError(BidderError.badInput("Unsupported currency, bidder only accepts USD."));
54+
}
55+
56+
final List<HttpRequest<BidRequest>> requests = new ArrayList<>();
57+
final List<BidderError> errors = new ArrayList<>();
58+
final List<Imp> imps = bidRequest.getImp();
59+
for (int i = 0; i < imps.size(); i++) {
60+
final Imp imp = imps.get(i);
61+
if (imp.getBanner() == null && imp.getVideo() == null && imp.getXNative() == null) {
62+
errors.add(BidderError.badInput(
63+
"imp[%d]: Unsupported media type, bidder does not support audio.".formatted(i)));
64+
65+
continue;
66+
}
67+
68+
final Imp modifiedImp = imp.getAudio() != null
69+
? imp.toBuilder().audio(null).build()
70+
: imp;
71+
requests.add(BidderUtil.defaultRequest(
72+
bidRequest.toBuilder().imp(Collections.singletonList(modifiedImp)).build(),
73+
endpointUrl, mapper));
74+
}
75+
76+
return Result.of(requests, errors);
77+
}
78+
79+
private static boolean hasNoAppOrSiteId(BidRequest bidRequest) {
80+
final App app = bidRequest.getApp();
81+
final Site site = bidRequest.getSite();
82+
return (app == null || StringUtils.isEmpty(app.getId()))
83+
&& (site == null || StringUtils.isEmpty(site.getId()));
84+
}
85+
86+
private static boolean isSupportedCurrency(List<String> currencies) {
87+
return CollectionUtils.isNotEmpty(currencies) && !currencies.contains(BID_CURRENCY);
88+
}
89+
90+
@Override
91+
public final Result<List<BidderBid>> makeBids(BidderCall<BidRequest> httpCall, BidRequest bidRequest) {
92+
final BidResponse response;
93+
94+
try {
95+
response = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class);
96+
} catch (DecodeException e) {
97+
return Result.withError(BidderError.badServerResponse(e.getMessage()));
98+
}
99+
100+
final List<BidderError> errors = new ArrayList<>();
101+
return Result.of(extractBids(response, errors), errors);
102+
}
103+
104+
private static List<BidderBid> extractBids(BidResponse bidResponse, List<BidderError> errors) {
105+
if (bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid())) {
106+
return Collections.emptyList();
107+
}
108+
return bidsFromResponse(bidResponse, errors);
109+
}
110+
111+
private static List<BidderBid> bidsFromResponse(BidResponse bidResponse, List<BidderError> errors) {
112+
return bidResponse.getSeatbid().stream()
113+
.filter(Objects::nonNull)
114+
.map(SeatBid::getBid)
115+
.filter(Objects::nonNull)
116+
.flatMap(Collection::stream)
117+
.filter(Objects::nonNull)
118+
.map(bid -> constructBidderBid(bid, bidResponse.getCur(), errors))
119+
.filter(Objects::nonNull)
120+
.toList();
121+
}
122+
123+
private static BidderBid constructBidderBid(Bid bid, String currency, List<BidderError> errors) {
124+
final BidType type = getBidType(bid);
125+
if (type != null) {
126+
return BidderBid.of(bid, type, currency);
127+
}
128+
129+
errors.add(BidderError.badServerResponse(
130+
"Failed to parse bid media type for impression %s.".formatted(bid.getImpid())));
131+
return null;
132+
}
133+
134+
private static BidType getBidType(Bid bid) {
135+
final JsonNode ext = bid.getExt();
136+
final JsonNode bidType = ext != null ? ext.at(BID_TYPE_POINTER) : null;
137+
if (bidType == null || !bidType.isTextual()) {
138+
return null;
139+
}
140+
141+
return switch (bidType.textValue()) {
142+
case "banner" -> BidType.banner;
143+
case "video" -> BidType.video;
144+
case "native" -> BidType.xNative;
145+
default -> null;
146+
};
147+
}
148+
149+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package org.prebid.server.spring.config.bidder;
2+
3+
import org.prebid.server.bidder.BidderDeps;
4+
import org.prebid.server.bidder.startio.StartioBidder;
5+
import org.prebid.server.json.JacksonMapper;
6+
import org.prebid.server.spring.config.bidder.model.BidderConfigurationProperties;
7+
import org.prebid.server.spring.config.bidder.util.BidderDepsAssembler;
8+
import org.prebid.server.spring.config.bidder.util.UsersyncerCreator;
9+
import org.prebid.server.spring.env.YamlPropertySourceFactory;
10+
import org.springframework.beans.factory.annotation.Value;
11+
import org.springframework.boot.context.properties.ConfigurationProperties;
12+
import org.springframework.context.annotation.Bean;
13+
import org.springframework.context.annotation.Configuration;
14+
import org.springframework.context.annotation.PropertySource;
15+
16+
import jakarta.validation.constraints.NotBlank;
17+
18+
@Configuration
19+
@PropertySource(value = "classpath:/bidder-config/startio.yaml", factory = YamlPropertySourceFactory.class)
20+
public class StartioBidderConfiguration {
21+
22+
private static final String BIDDER_NAME = "startio";
23+
24+
@Bean("startioConfigurationProperties")
25+
@ConfigurationProperties("adapters.startio")
26+
BidderConfigurationProperties configurationProperties() {
27+
return new BidderConfigurationProperties();
28+
}
29+
30+
@Bean
31+
BidderDeps startioBidderDeps(BidderConfigurationProperties startioConfigurationProperties,
32+
@NotBlank @Value("${external-url}") String externalUrl,
33+
JacksonMapper mapper) {
34+
35+
return BidderDepsAssembler.forBidder(BIDDER_NAME)
36+
.withConfig(startioConfigurationProperties)
37+
.usersyncerCreator(UsersyncerCreator.create(externalUrl))
38+
.bidderCreator(config -> new StartioBidder(config.getEndpoint(), mapper))
39+
.assemble();
40+
}
41+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
adapters:
2+
startio:
3+
endpoint: http://pbs-rtb.startappnetwork.com/1.3/2.5/getbid?account=pbs
4+
meta-info:
5+
maintainer-email: [email protected]
6+
app-media-types:
7+
- banner
8+
- video
9+
- native
10+
site-media-types:
11+
- banner
12+
- video
13+
- native
14+
vendor-id: 1216
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-04/schema#",
3+
"title": "Start.io Adapter Params",
4+
"description": "A schema which validates params accepted by the Start.io adapter",
5+
"type": "object",
6+
"properties": {},
7+
"required": []
8+
}

0 commit comments

Comments
 (0)