Skip to content

Commit 9e9bf59

Browse files
New Adapter: Clydo (prebid#4299)
1 parent db7a3ae commit 9e9bf59

12 files changed

Lines changed: 837 additions & 0 deletions

File tree

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
package org.prebid.server.bidder.clydo;
2+
3+
import com.fasterxml.jackson.core.type.TypeReference;
4+
import com.iab.openrtb.request.BidRequest;
5+
import com.iab.openrtb.request.Device;
6+
import com.iab.openrtb.request.Imp;
7+
import com.iab.openrtb.response.Bid;
8+
import com.iab.openrtb.response.BidResponse;
9+
import com.iab.openrtb.response.SeatBid;
10+
import io.netty.handler.codec.http.HttpHeaderValues;
11+
import io.vertx.core.MultiMap;
12+
import org.prebid.server.bidder.Bidder;
13+
import org.prebid.server.bidder.model.BidderBid;
14+
import org.prebid.server.bidder.model.BidderCall;
15+
import org.prebid.server.bidder.model.BidderError;
16+
import org.prebid.server.bidder.model.HttpRequest;
17+
import org.prebid.server.bidder.model.Result;
18+
import org.prebid.server.exception.PreBidException;
19+
import org.prebid.server.json.DecodeException;
20+
import org.prebid.server.json.JacksonMapper;
21+
import org.prebid.server.proto.openrtb.ext.ExtPrebid;
22+
import org.prebid.server.proto.openrtb.ext.request.clydo.ExtImpClydo;
23+
import org.prebid.server.proto.openrtb.ext.response.BidType;
24+
import org.prebid.server.util.BidderUtil;
25+
import org.prebid.server.util.HttpUtil;
26+
import org.prebid.server.util.ObjectUtil;
27+
28+
import java.util.ArrayList;
29+
import java.util.Collections;
30+
import java.util.HashMap;
31+
import java.util.List;
32+
import java.util.Map;
33+
import java.util.Objects;
34+
import java.util.stream.Collectors;
35+
36+
public class ClydoBidder implements Bidder<BidRequest> {
37+
38+
private static final TypeReference<ExtPrebid<?, ExtImpClydo>> CLYDO_EXT_TYPE_REFERENCE =
39+
new TypeReference<>() {
40+
};
41+
42+
private static final String REGION_MACRO = "{{Region}}";
43+
private static final String PARTNER_ID_MACRO = "{{PartnerId}}";
44+
private static final String DEFAULT_REGION = "us";
45+
private static final String X_OPENRTB_VERSION = "2.5";
46+
47+
private final String endpointUrl;
48+
private final JacksonMapper mapper;
49+
50+
public ClydoBidder(String endpointUrl, JacksonMapper mapper) {
51+
this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl));
52+
this.mapper = Objects.requireNonNull(mapper);
53+
}
54+
55+
@Override
56+
public Result<List<HttpRequest<BidRequest>>> makeHttpRequests(BidRequest request) {
57+
final List<BidderError> errors = new ArrayList<>();
58+
final List<HttpRequest<BidRequest>> httpRequests = new ArrayList<>();
59+
60+
for (Imp imp : request.getImp()) {
61+
try {
62+
final ExtImpClydo extImpClydo = parseExtImp(imp);
63+
final HttpRequest<BidRequest> httpRequest = makeHttpRequest(request, imp, extImpClydo);
64+
httpRequests.add(httpRequest);
65+
} catch (PreBidException e) {
66+
errors.add(BidderError.badInput(e.getMessage()));
67+
}
68+
}
69+
70+
if (httpRequests.isEmpty()) {
71+
return Result.withError(BidderError.badInput("found no valid impressions"));
72+
}
73+
74+
return Result.of(httpRequests, errors);
75+
}
76+
77+
private ExtImpClydo parseExtImp(Imp imp) {
78+
try {
79+
return mapper.mapper().convertValue(imp.getExt(), CLYDO_EXT_TYPE_REFERENCE).getBidder();
80+
} catch (IllegalArgumentException e) {
81+
throw new PreBidException("Cannot deserialize ExtImpClydo: " + e.getMessage());
82+
}
83+
}
84+
85+
private HttpRequest<BidRequest> makeHttpRequest(BidRequest request, Imp imp, ExtImpClydo extImpClydo) {
86+
final BidRequest outgoingRequest = request.toBuilder().imp(List.of(imp)).build();
87+
88+
return BidderUtil.defaultRequest(
89+
outgoingRequest,
90+
constructHeaders(request),
91+
resolveUrl(endpointUrl, extImpClydo), mapper);
92+
}
93+
94+
private static MultiMap constructHeaders(BidRequest bidRequest) {
95+
final Device device = bidRequest.getDevice();
96+
final MultiMap headers = HttpUtil.headers();
97+
98+
headers.set(HttpUtil.X_OPENRTB_VERSION_HEADER, X_OPENRTB_VERSION);
99+
headers.set(HttpUtil.ACCEPT_HEADER, HttpHeaderValues.APPLICATION_JSON);
100+
headers.set(HttpUtil.CONTENT_TYPE_HEADER, HttpUtil.APPLICATION_JSON_CONTENT_TYPE);
101+
HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.X_FORWARDED_FOR_HEADER,
102+
ObjectUtil.getIfNotNull(device, Device::getIpv6));
103+
HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.X_FORWARDED_FOR_HEADER,
104+
ObjectUtil.getIfNotNull(device, Device::getIp));
105+
HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.USER_AGENT_HEADER,
106+
ObjectUtil.getIfNotNull(device, Device::getUa));
107+
108+
return headers;
109+
}
110+
111+
private static String resolveUrl(String endpoint, ExtImpClydo extImp) {
112+
return endpoint
113+
.replace(REGION_MACRO, getRegionInfo(extImp))
114+
.replace(PARTNER_ID_MACRO, HttpUtil.encodeUrl(extImp.getPartnerId()));
115+
}
116+
117+
private static String getRegionInfo(ExtImpClydo extImp) {
118+
final String region = extImp.getRegion();
119+
120+
return switch (region) {
121+
case "us", "usw", "eu", "apac" -> region;
122+
case null, default -> DEFAULT_REGION;
123+
};
124+
}
125+
126+
@Override
127+
public Result<List<BidderBid>> makeBids(BidderCall<BidRequest> httpCall, BidRequest bidRequest) {
128+
try {
129+
final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class);
130+
final List<BidderBid> bidderBids = extractBids(bidRequest, bidResponse);
131+
return Result.withValues(bidderBids);
132+
} catch (DecodeException | PreBidException e) {
133+
return Result.withError(BidderError.badServerResponse(e.getMessage()));
134+
}
135+
}
136+
137+
private List<BidderBid> extractBids(BidRequest bidRequest, BidResponse bidResponse) {
138+
if (bidResponse == null || bidResponse.getSeatbid() == null || bidResponse.getSeatbid().isEmpty()) {
139+
return Collections.emptyList();
140+
}
141+
142+
final Map<String, BidType> impIdToBidTypeMap = buildImpIdToBidTypeMap(bidRequest);
143+
144+
return bidResponse.getSeatbid().stream()
145+
.filter(Objects::nonNull)
146+
.map(SeatBid::getBid)
147+
.filter(Objects::nonNull)
148+
.flatMap(List::stream)
149+
.filter(Objects::nonNull)
150+
.map(bid -> BidderBid.of(bid, resolveBidType(bid, impIdToBidTypeMap), bidResponse.getCur()))
151+
.collect(Collectors.toList());
152+
}
153+
154+
private static Map<String, BidType> buildImpIdToBidTypeMap(BidRequest bidRequest) {
155+
final Map<String, BidType> impIdToBidTypeMap = new HashMap<>();
156+
for (Imp imp : bidRequest.getImp()) {
157+
final String impId = imp.getId();
158+
final BidType bidType = determineBidType(imp);
159+
impIdToBidTypeMap.put(impId, bidType);
160+
}
161+
162+
return impIdToBidTypeMap;
163+
}
164+
165+
private static BidType determineBidType(Imp imp) {
166+
if (imp.getAudio() != null) {
167+
return BidType.audio;
168+
} else if (imp.getVideo() != null) {
169+
return BidType.video;
170+
} else if (imp.getXNative() != null) {
171+
return BidType.xNative;
172+
} else if (imp.getBanner() != null) {
173+
return BidType.banner;
174+
}
175+
176+
throw new PreBidException("Failed to get media type");
177+
}
178+
179+
private static BidType resolveBidType(Bid bid, Map<String, BidType> impIdToBidTypeMap) {
180+
return impIdToBidTypeMap.getOrDefault(bid.getImpid(), BidType.banner);
181+
}
182+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package org.prebid.server.proto.openrtb.ext.request.clydo;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import lombok.Value;
5+
6+
@Value(staticConstructor = "of")
7+
public class ExtImpClydo {
8+
9+
@JsonProperty("partnerId")
10+
String partnerId;
11+
12+
String region;
13+
}
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.clydo.ClydoBidder;
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/clydo.yaml", factory = YamlPropertySourceFactory.class)
20+
public class ClydoConfiguration {
21+
22+
private static final String BIDDER_NAME = "clydo";
23+
24+
@Bean("clydoConfigurationProperties")
25+
@ConfigurationProperties("adapters.clydo")
26+
BidderConfigurationProperties configurationProperties() {
27+
return new BidderConfigurationProperties();
28+
}
29+
30+
@Bean
31+
BidderDeps clydoBidderDeps(BidderConfigurationProperties clydoConfigurationProperties,
32+
@NotBlank @Value("${external-url}") String externalUrl,
33+
JacksonMapper mapper) {
34+
35+
return BidderDepsAssembler.forBidder(BIDDER_NAME)
36+
.withConfig(clydoConfigurationProperties)
37+
.usersyncerCreator(UsersyncerCreator.create(externalUrl))
38+
.bidderCreator(config -> new ClydoBidder(config.getEndpoint(), mapper))
39+
.assemble();
40+
}
41+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
adapters:
2+
clydo:
3+
endpoint: http://region={{Region}}.clydo.io/partnerId={{PartnerId}}
4+
modifying-vast-xml-allowed: true
5+
endpoint-compression: gzip
6+
geoscope:
7+
- global
8+
meta-info:
9+
maintainer-email: cto@clydo.io
10+
app-media-types:
11+
- banner
12+
- video
13+
- audio
14+
- native
15+
site-media-types:
16+
- banner
17+
- video
18+
- audio
19+
- native
20+
supported-vendors:
21+
vendor-id: 0
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-04/schema#",
3+
"title": "Clydo Adapter Params",
4+
"description": "A schema which validates params accepted by the Clydo adapter",
5+
"type": "object",
6+
"properties": {
7+
"partnerId": {
8+
"type": "string",
9+
"description": "Partner ID",
10+
"minLength": 1
11+
},
12+
"region": {
13+
"type": "string",
14+
"description": "Regional endpoint identifier (us, usw, eu, apac)",
15+
"enum": ["us", "usw", "eu", "apac"]
16+
}
17+
},
18+
19+
"required": ["partnerId"]
20+
}

0 commit comments

Comments
 (0)